phobos/std/format/internal/write.d

2747 lines
80 KiB
D

// Written in the D programming language.
/*
Copyright: Copyright The D Language Foundation 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/internal/write.d)
*/
module std.format.internal.write;
import std.exception;
import std.meta;
import std.range.primitives;
import std.traits;
import std.format;
/*
`bool`s are formatted as `"true"` or `"false"` with `%s` and as `1` or
`0` with integral-specific format specs.
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
{
BooleanTypeOf!T val = obj;
if (f.spec == 's')
writeAligned(w, val ? "true" : "false", f);
else
formatValueImpl(w, cast(int) val, f);
}
@safe pure unittest
{
assertCTFEable!(
{
formatTest( false, "false" );
formatTest( true, "true" );
});
}
@system unittest
{
class C1 { bool val; alias val this; this(bool v){ val = v; } }
class C2 { bool val; alias val this; this(bool v){ val = v; }
override string toString() const { return "C"; } }
formatTest( new C1(false), "false" );
formatTest( new C1(true), "true" );
formatTest( new C2(false), "C" );
formatTest( new C2(true), "C" );
struct S1 { bool val; alias val this; }
struct S2 { bool val; alias val this;
string toString() const { return "S"; } }
formatTest( S1(false), "false" );
formatTest( S1(true), "true" );
formatTest( S2(false), "S" );
formatTest( S2(true), "S" );
}
@safe pure unittest
{
string t1 = format("[%6s] [%6s] [%-6s]", true, false, true);
assert(t1 == "[ true] [ false] [true ]");
string t2 = format("[%3s] [%-2s]", true, false);
assert(t2 == "[true] [false]");
}
/*
`null` literal is formatted as `"null"`
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
if (is(immutable T == immutable typeof(null)) && !is(T == enum) && !hasToString!(T, Char))
{
const spec = f.spec;
enforceFmt(spec == 's',
"null literal cannot match %" ~ spec);
writeAligned(w, "null", f);
}
@safe pure unittest
{
assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p');
assertCTFEable!(
{
formatTest( null, "null" );
});
}
@safe pure unittest
{
string t = format("[%6s] [%-6s]", null, null);
assert(t == "[ null] [null ]");
}
/*
Integrals are formatted like $(REF printf, core, stdc, stdio).
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
{
alias U = IntegralTypeOf!T;
U val = obj; // Extracting alias this may be impure/system/may-throw
const spec = f.spec;
if (spec == 'r')
{
// raw write, skip all else and write the thing
auto raw = (ref val)@trusted{
return (cast(const char*) &val)[0 .. val.sizeof];
}(val);
if (needToSwapEndianess(f))
{
foreach_reverse (c; raw)
put(w, c);
}
else
{
foreach (c; raw)
put(w, c);
}
return;
}
immutable uint base =
spec == 'x' || spec == 'X' ? 16 :
spec == 'o' ? 8 :
spec == 'b' ? 2 :
spec == 's' || spec == 'd' || spec == 'u' ? 10 :
0;
enforceFmt(base > 0,
"incompatible format character for integral argument: %" ~ spec);
// Forward on to formatIntegral to handle both U and const(U)
// Saves duplication of code for both versions.
static if (is(ucent) && (is(U == cent) || is(U == ucent)))
alias C = U;
else static if (isSigned!U)
alias C = long;
else
alias C = ulong;
formatIntegral(w, cast(C) val, f, base, Unsigned!U.max);
}
private void formatIntegral(Writer, T, Char)(ref Writer w, const(T) val, scope const ref FormatSpec!Char fs,
uint base, ulong mask)
{
T arg = val;
immutable negative = (base == 10 && arg < 0);
if (negative)
{
arg = -arg;
}
// All unsigned integral types should fit in ulong.
static if (is(ucent) && is(typeof(arg) == ucent))
formatUnsigned(w, (cast(ucent) arg) & mask, fs, base, negative);
else
formatUnsigned(w, (cast(ulong) arg) & mask, fs, base, negative);
}
private void formatUnsigned(Writer, T, Char)
(ref Writer w, T arg, scope const ref FormatSpec!Char fs, uint base, bool negative)
{
/* Write string:
* leftpad prefix1 prefix2 zerofill digits rightpad
*/
/* Convert arg to digits[].
* Note that 0 becomes an empty digits[]
*/
char[64] buffer = void; // 64 bits in base 2 at most
char[] digits;
if (arg < base && base <= 10 && arg)
{
// Most numbers are a single digit - avoid expensive divide
buffer[0] = cast(char)(arg + '0');
digits = buffer[0 .. 1];
}
else
{
size_t i = buffer.length;
while (arg)
{
--i;
char c = cast(char) (arg % base);
arg /= base;
if (c < 10)
buffer[i] = cast(char)(c + '0');
else
buffer[i] = cast(char)(c + (fs.spec == 'x' ? 'a' - 10 : 'A' - 10));
}
digits = buffer[i .. $]; // got the digits without the sign
}
immutable precision = (fs.precision == fs.UNSPECIFIED) ? 1 : fs.precision;
char padChar = 0;
if (!fs.flDash)
{
padChar = (fs.flZero && fs.precision == fs.UNSPECIFIED) ? '0' : ' ';
}
// Compute prefix1 and prefix2
char prefix1 = 0;
char prefix2 = 0;
if (base == 10)
{
if (negative)
prefix1 = '-';
else if (fs.flPlus)
prefix1 = '+';
else if (fs.flSpace)
prefix1 = ' ';
}
else if (base == 16 && fs.flHash && digits.length)
{
prefix1 = '0';
prefix2 = fs.spec == 'x' ? 'x' : 'X';
}
// adjust precision to print a '0' for octal if alternate format is on
else if (base == 8 && fs.flHash &&
(precision <= 1 || precision <= digits.length) && // too low precision
digits.length > 0)
prefix1 = '0';
size_t zerofill = precision > digits.length ? precision - digits.length : 0;
size_t leftpad = 0;
size_t rightpad = 0;
immutable prefixWidth = (prefix1 != 0) + (prefix2 != 0);
size_t finalWidth, separatorsCount;
if (fs.flSeparator != 0)
{
finalWidth = prefixWidth + digits.length + ((digits.length > 0) ? (digits.length - 1) / fs.separators : 0);
if (finalWidth < fs.width)
finalWidth = fs.width + (padChar == '0') * (((fs.width - prefixWidth) % (fs.separators + 1) == 0) ? 1 : 0);
separatorsCount = (padChar == '0') ? (finalWidth - prefixWidth - 1) / (fs.separators + 1) :
((digits.length > 0) ? (digits.length - 1) / fs.separators : 0);
}
else
{
import std.algorithm.comparison : max;
finalWidth = max(fs.width, prefixWidth + digits.length);
}
immutable ptrdiff_t spacesToPrint =
finalWidth - (
+ prefixWidth
+ zerofill
+ digits.length
+ separatorsCount
);
if (spacesToPrint > 0) // need to do some padding
{
if (padChar == '0')
zerofill += spacesToPrint;
else if (padChar)
leftpad = spacesToPrint;
else
rightpad = spacesToPrint;
}
// Print
foreach (i ; 0 .. leftpad)
put(w, ' ');
if (prefix1) put(w, prefix1);
if (prefix2) put(w, prefix2);
if (fs.flSeparator)
{
if (zerofill > 0)
{
put(w, '0');
--zerofill;
}
int j = cast(int) (finalWidth - prefixWidth - separatorsCount - 1);
for (size_t i = 0; i < zerofill; ++i, --j)
{
if (j % fs.separators == 0)
{
put(w, fs.separatorChar);
}
put(w, '0');
}
}
else
{
foreach (i ; 0 .. zerofill)
put(w, '0');
}
if (fs.flSeparator)
{
for (size_t j = 0; j < digits.length; ++j)
{
if (((j != 0) || ((spacesToPrint > 0) && (padChar == '0'))) && (digits.length - j) % fs.separators == 0)
{
put(w, fs.separatorChar);
}
put(w, digits[j]);
}
}
else
{
put(w, digits);
}
foreach (i ; 0 .. rightpad)
put(w, ' ');
}
// https://issues.dlang.org/show_bug.cgi?id=18838
@safe pure unittest
{
assert("%12,d".format(0) == " 0");
}
@safe pure unittest
{
assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c');
assertCTFEable!(
{
formatTest(9, "9");
formatTest( 10, "10" );
});
}
@system unittest
{
class C1 { long val; alias val this; this(long v){ val = v; } }
class C2 { long val; alias val this; this(long v){ val = v; }
override string toString() const { return "C"; } }
formatTest( new C1(10), "10" );
formatTest( new C2(10), "C" );
struct S1 { long val; alias val this; }
struct S2 { long val; alias val this;
string toString() const { return "S"; } }
formatTest( S1(10), "10" );
formatTest( S2(10), "S" );
}
// https://issues.dlang.org/show_bug.cgi?id=9117
@safe unittest
{
static struct Frop {}
static struct Foo
{
int n = 0;
alias n this;
T opCast(T) () if (is(T == Frop))
{
return Frop();
}
string toString()
{
return "Foo";
}
}
static struct Bar
{
Foo foo;
alias foo this;
string toString()
{
return "Bar";
}
}
const(char)[] result;
void put(scope const char[] s){ result ~= s; }
Foo foo;
formattedWrite(&put, "%s", foo); // OK
assert(result == "Foo");
result = null;
Bar bar;
formattedWrite(&put, "%s", bar); // NG
assert(result == "Bar");
result = null;
int i = 9;
formattedWrite(&put, "%s", 9);
assert(result == "9");
}
// https://issues.dlang.org/show_bug.cgi?id=20064
@safe unittest
{
assert(format( "%03,d", 1234) == "1,234");
assert(format( "%04,d", 1234) == "1,234");
assert(format( "%05,d", 1234) == "1,234");
assert(format( "%06,d", 1234) == "01,234");
assert(format( "%07,d", 1234) == "001,234");
assert(format( "%08,d", 1234) == "0,001,234");
assert(format( "%09,d", 1234) == "0,001,234");
assert(format("%010,d", 1234) == "00,001,234");
assert(format("%011,d", 1234) == "000,001,234");
assert(format("%012,d", 1234) == "0,000,001,234");
assert(format("%013,d", 1234) == "0,000,001,234");
assert(format("%014,d", 1234) == "00,000,001,234");
assert(format("%015,d", 1234) == "000,000,001,234");
assert(format("%016,d", 1234) == "0,000,000,001,234");
assert(format("%017,d", 1234) == "0,000,000,001,234");
assert(format( "%03,d", -1234) == "-1,234");
assert(format( "%04,d", -1234) == "-1,234");
assert(format( "%05,d", -1234) == "-1,234");
assert(format( "%06,d", -1234) == "-1,234");
assert(format( "%07,d", -1234) == "-01,234");
assert(format( "%08,d", -1234) == "-001,234");
assert(format( "%09,d", -1234) == "-0,001,234");
assert(format("%010,d", -1234) == "-0,001,234");
assert(format("%011,d", -1234) == "-00,001,234");
assert(format("%012,d", -1234) == "-000,001,234");
assert(format("%013,d", -1234) == "-0,000,001,234");
assert(format("%014,d", -1234) == "-0,000,001,234");
assert(format("%015,d", -1234) == "-00,000,001,234");
assert(format("%016,d", -1234) == "-000,000,001,234");
assert(format("%017,d", -1234) == "-0,000,000,001,234");
}
@safe pure unittest
{
string t1 = format("[%6s] [%-6s]", 123, 123);
assert(t1 == "[ 123] [123 ]");
string t2 = format("[%6s] [%-6s]", -123, -123);
assert(t2 == "[ -123] [-123 ]");
}
/*
Floating-point values are formatted like $(REF printf, core, stdc, stdio)
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
{
import std.algorithm.comparison : min;
import std.algorithm.searching : find;
import std.string : indexOf, indexOfAny, indexOfNeither;
import std.math : isInfinity, isNaN, signbit;
import std.ascii : isUpper;
string nanInfStr(scope const ref FormatSpec!Char f, const bool nan,
const bool inf, const int sb, const bool up) @safe pure nothrow
{
return nan
? up
? sb ? "-NAN" : f.flPlus ? "+NAN" : (f.flSpace ? " NAN" : "NAN")
: sb ? "-nan" : f.flPlus ? "+nan" : (f.flSpace ? " nan" : "nan")
: inf
? up
? sb ? "-INF" : f.flPlus ? "+INF" : (f.flSpace ? " INF" : "INF")
: sb ? "-inf" : f.flPlus ? "+inf" : (f.flSpace ? " inf" : "inf")
: "";
}
FloatingPointTypeOf!T val = obj;
const char spec = f.spec;
if (spec == 'r')
{
// raw write, skip all else and write the thing
auto raw = (ref val)@trusted{
return (cast(const char*) &val)[0 .. val.sizeof];
}(val);
if (needToSwapEndianess(f))
{
foreach_reverse (c; raw)
put(w, c);
}
else
{
foreach (c; raw)
put(w, c);
}
return;
}
enforceFmt(find("fgFGaAeEs", spec).length,
"incompatible format character for floating point argument: %" ~ spec);
FormatSpec!Char fs = f; // fs is copy for change its values.
const spec2 = spec == 's' ? 'g' : spec;
version (CRuntime_Microsoft)
{
// convert early to get "inf" in case of overflow
// windows handels inf and nan strange
// https://devblogs.microsoft.com/oldnewthing/20130228-01/?p=5103
immutable double tval = val;
}
else
{
alias tval = val;
}
const nan = isNaN(tval);
const inf = isInfinity(tval);
char[512] buf2 = void;
size_t len;
char[] buf;
if (fs.spec=='a' || fs.spec=='A' || fs.spec=='e' || fs.spec=='E' || fs.spec=='f' || fs.spec=='F')
{
static if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig))
{
import std.math;
import std.format.internal.floats : RoundingMode, printFloat;
auto mode = RoundingMode.toNearestTiesToEven;
if (!__ctfe)
{
// std.math's FloatingPointControl isn't available on all target platforms
static if (is(FloatingPointControl))
{
switch (FloatingPointControl.rounding)
{
case FloatingPointControl.roundUp:
mode = RoundingMode.up;
break;
case FloatingPointControl.roundDown:
mode = RoundingMode.down;
break;
case FloatingPointControl.roundToZero:
mode = RoundingMode.toZero;
break;
case FloatingPointControl.roundToNearest:
mode = RoundingMode.toNearestTiesToEven;
break;
default: assert(false);
}
}
}
buf = printFloat(buf2[], val, fs, mode);
len = buf.length;
}
else
goto useSnprintf;
}
else
{
useSnprintf:
import std.format.internal.floats : ctfpMessage;
enforceFmt(!__ctfe, ctfpMessage);
if (nan || inf)
{
const sb = signbit(tval);
const up = isUpper(spec);
string ns = nanInfStr(f, nan, inf, sb, up);
FormatSpec!Char co;
co.spec = 's';
co.width = f.width;
co.flDash = f.flDash;
formatValue(w, ns, co);
return;
}
char[1 /*%*/ + 5 /*flags*/ + 3 /*width.prec*/ + 2 /*format*/
+ 1 /*\0*/] sprintfSpec = void;
sprintfSpec[0] = '%';
uint i = 1;
if (fs.flDash) sprintfSpec[i++] = '-';
if (fs.flPlus) sprintfSpec[i++] = '+';
if (fs.flZero) sprintfSpec[i++] = '0';
if (fs.flSpace) sprintfSpec[i++] = ' ';
if (fs.flHash) sprintfSpec[i++] = '#';
sprintfSpec[i .. i + 3] = "*.*";
i += 3;
if (is(immutable typeof(val) == immutable real)) sprintfSpec[i++] = 'L';
sprintfSpec[i++] = spec2;
sprintfSpec[i] = 0;
//printf("format: '%s'; geeba: %g\n", sprintfSpec.ptr, val);
//writefln("'%s'", sprintfSpec[0 .. i]);
immutable n = ()@trusted{
import core.stdc.stdio : snprintf;
return snprintf(buf2.ptr, buf2.length,
sprintfSpec.ptr,
fs.width,
// negative precision is same as no precision specified
fs.precision == fs.UNSPECIFIED ? -1 : fs.precision,
tval);
}();
enforceFmt(n >= 0,
"floating point formatting failure");
len = min(n, buf2.length-1);
buf = buf2;
}
if (fs.flSeparator && !inf && !nan)
{
ptrdiff_t indexOfRemovable()
{
if (len < 2)
return -1;
size_t start = (buf[0 .. 1].indexOfAny(" 0123456789") == -1) ? 1 : 0;
if (len < 2 + start)
return -1;
if ((buf[start] == ' ') || (buf[start] == '0' && buf[start + 1] != '.'))
return start;
return -1;
}
ptrdiff_t dot, firstDigit, ePos, dotIdx, firstLen;
size_t separatorScoreCnt;
while (true)
{
dot = buf[0 .. len].indexOf('.');
firstDigit = buf[0 .. len].indexOfAny("0123456789");
ePos = buf[0 .. len].indexOf('e');
dotIdx = dot == -1 ? ePos == -1 ? len : ePos : dot;
firstLen = dotIdx - firstDigit;
separatorScoreCnt = (firstLen > 0) ? (firstLen - 1) / fs.separators : 0;
ptrdiff_t removableIdx = (len + separatorScoreCnt > fs.width) ? indexOfRemovable() : -1;
if ((removableIdx != -1) &&
((firstLen - (buf[removableIdx] == '0' ? 2 : 1)) / fs.separators + len - 1 >= fs.width))
{
buf[removableIdx .. $ - 1] = buf.dup[removableIdx + 1 .. $];
len--;
}
else
break;
}
immutable afterDotIdx = (ePos != -1) ? ePos : len;
// plus, minus, prefix
if (firstDigit > 0)
{
put(w, buf[0 .. firstDigit]);
}
// digits until dot with separator
for (auto j = 0; j < firstLen; ++j)
{
if (j > 0 && (firstLen - j) % fs.separators == 0)
{
put(w, fs.separatorChar);
}
put(w, buf[j + firstDigit]);
}
// print dot for decimal numbers only or with '#' format specifier
if (dot != -1 || fs.flHash)
{
put(w, '.');
}
// digits after dot
for (auto j = dotIdx + 1; j < afterDotIdx; ++j)
{
put(w, buf[j]);
}
// rest
if (ePos != -1)
{
put(w, buf[afterDotIdx .. len]);
}
}
else
{
put(w, buf[0 .. len]);
}
}
@safe unittest
{
assert(format("%.1f", 1337.7) == "1337.7");
assert(format("%,3.2f", 1331.982) == "1,331.98");
assert(format("%,3.0f", 1303.1982) == "1,303");
assert(format("%#,3.4f", 1303.1982) == "1,303.1982");
assert(format("%#,3.0f", 1303.1982) == "1,303.");
}
@safe /*pure*/ unittest // formatting floating point values is now impure
{
import std.conv : to;
assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd');
static foreach (T; AliasSeq!(float, double, real))
{
formatTest( to!( T)(5.5), "5.5" );
formatTest( to!( const T)(5.5), "5.5" );
formatTest( to!(immutable T)(5.5), "5.5" );
formatTest( T.nan, "nan" );
}
}
@system unittest
{
formatTest( 2.25, "2.25" );
class C1 { double val; alias val this; this(double v){ val = v; } }
class C2 { double val; alias val this; this(double v){ val = v; }
override string toString() const { return "C"; } }
formatTest( new C1(2.25), "2.25" );
formatTest( new C2(2.25), "C" );
struct S1 { double val; alias val this; }
struct S2 { double val; alias val this;
string toString() const { return "S"; } }
formatTest( S1(2.25), "2.25" );
formatTest( S2(2.25), "S" );
}
// https://issues.dlang.org/show_bug.cgi?id=19939
@safe unittest
{
assert(format("^%13,3.2f$", 1.00) == "^ 1.00$");
assert(format("^%13,3.2f$", 10.00) == "^ 10.00$");
assert(format("^%13,3.2f$", 100.00) == "^ 100.00$");
assert(format("^%13,3.2f$", 1_000.00) == "^ 1,000.00$");
assert(format("^%13,3.2f$", 10_000.00) == "^ 10,000.00$");
assert(format("^%13,3.2f$", 100_000.00) == "^ 100,000.00$");
assert(format("^%13,3.2f$", 1_000_000.00) == "^ 1,000,000.00$");
assert(format("^%13,3.2f$", 10_000_000.00) == "^10,000,000.00$");
}
// https://issues.dlang.org/show_bug.cgi?id=20069
@safe unittest
{
assert(format("%012,f", -1234.0) == "-1,234.000000");
assert(format("%013,f", -1234.0) == "-1,234.000000");
assert(format("%014,f", -1234.0) == "-01,234.000000");
assert(format("%011,f", 1234.0) == "1,234.000000");
assert(format("%012,f", 1234.0) == "1,234.000000");
assert(format("%013,f", 1234.0) == "01,234.000000");
assert(format("%014,f", 1234.0) == "001,234.000000");
assert(format("%015,f", 1234.0) == "0,001,234.000000");
assert(format("%016,f", 1234.0) == "0,001,234.000000");
assert(format( "%08,.2f", -1234.0) == "-1,234.00");
assert(format( "%09,.2f", -1234.0) == "-1,234.00");
assert(format("%010,.2f", -1234.0) == "-01,234.00");
assert(format("%011,.2f", -1234.0) == "-001,234.00");
assert(format("%012,.2f", -1234.0) == "-0,001,234.00");
assert(format("%013,.2f", -1234.0) == "-0,001,234.00");
assert(format("%014,.2f", -1234.0) == "-00,001,234.00");
assert(format( "%08,.2f", 1234.0) == "1,234.00");
assert(format( "%09,.2f", 1234.0) == "01,234.00");
assert(format("%010,.2f", 1234.0) == "001,234.00");
assert(format("%011,.2f", 1234.0) == "0,001,234.00");
assert(format("%012,.2f", 1234.0) == "0,001,234.00");
assert(format("%013,.2f", 1234.0) == "00,001,234.00");
assert(format("%014,.2f", 1234.0) == "000,001,234.00");
assert(format("%015,.2f", 1234.0) == "0,000,001,234.00");
assert(format("%016,.2f", 1234.0) == "0,000,001,234.00");
}
@safe unittest
{
import std.math;
// std.math's FloatingPointControl isn't available on all target platforms
static if (is(FloatingPointControl))
{
assert(FloatingPointControl.rounding == FloatingPointControl.roundToNearest);
}
// issue 20320
real a = 0.16;
real b = 0.016;
assert(format("%.1f", a) == "0.2");
// assert(format("%.2f", b) == "0.02"); // Windows still fails here...
double a1 = 0.16;
double b1 = 0.016;
assert(format("%.1f", a1) == "0.2");
assert(format("%.2f", b1) == "0.02");
// issue 9889
assert(format("%.1f", 0.09) == "0.1");
assert(format("%.1f", -0.09) == "-0.1");
assert(format("%.1f", 0.095) == "0.1");
assert(format("%.1f", -0.095) == "-0.1");
assert(format("%.1f", 0.094) == "0.1");
assert(format("%.1f", -0.094) == "-0.1");
}
@safe unittest
{
double a = 123.456;
double b = -123.456;
double c = 123.0;
assert(format("%10.4f",a) == " 123.4560");
assert(format("%-10.4f",a) == "123.4560 ");
assert(format("%+10.4f",a) == " +123.4560");
assert(format("% 10.4f",a) == " 123.4560");
assert(format("%010.4f",a) == "00123.4560");
assert(format("%#10.4f",a) == " 123.4560");
assert(format("%10.4f",b) == " -123.4560");
assert(format("%-10.4f",b) == "-123.4560 ");
assert(format("%+10.4f",b) == " -123.4560");
assert(format("% 10.4f",b) == " -123.4560");
assert(format("%010.4f",b) == "-0123.4560");
assert(format("%#10.4f",b) == " -123.4560");
assert(format("%10.0f",c) == " 123");
assert(format("%-10.0f",c) == "123 ");
assert(format("%+10.0f",c) == " +123");
assert(format("% 10.0f",c) == " 123");
assert(format("%010.0f",c) == "0000000123");
assert(format("%#10.0f",c) == " 123.");
assert(format("%+010.4f",a) == "+0123.4560");
assert(format("% 010.4f",a) == " 0123.4560");
assert(format("% +010.4f",a) == "+0123.4560");
}
@safe unittest
{
string t1 = format("[%6s] [%-6s]", 12.3, 12.3);
assert(t1 == "[ 12.3] [12.3 ]");
string t2 = format("[%6s] [%-6s]", -12.3, -12.3);
assert(t2 == "[ -12.3] [-12.3 ]");
}
// https://issues.dlang.org/show_bug.cgi?id=20396
@safe unittest
{
import std.math : nextUp;
assert(format!"%a"(nextUp(0.0f)) == "0x0.000002p-126");
assert(format!"%a"(nextUp(0.0)) == "0x0.0000000000001p-1022");
}
// https://issues.dlang.org/show_bug.cgi?id=20371
@safe unittest
{
assert(format!"%.1000a"(1.0).length == 1007);
assert(format!"%.600f"(0.1).length == 602);
}
@safe unittest
{
import std.math;
// std.math's FloatingPointControl isn't available on all target platforms
static if (is(FloatingPointControl))
{
FloatingPointControl fpctrl;
fpctrl.rounding = FloatingPointControl.roundUp;
assert(format!"%.0e"(3.5) == "4e+00");
assert(format!"%.0e"(4.5) == "5e+00");
assert(format!"%.0e"(-3.5) == "-3e+00");
assert(format!"%.0e"(-4.5) == "-4e+00");
fpctrl.rounding = FloatingPointControl.roundDown;
assert(format!"%.0e"(3.5) == "3e+00");
assert(format!"%.0e"(4.5) == "4e+00");
assert(format!"%.0e"(-3.5) == "-4e+00");
assert(format!"%.0e"(-4.5) == "-5e+00");
fpctrl.rounding = FloatingPointControl.roundToZero;
assert(format!"%.0e"(3.5) == "3e+00");
assert(format!"%.0e"(4.5) == "4e+00");
assert(format!"%.0e"(-3.5) == "-3e+00");
assert(format!"%.0e"(-4.5) == "-4e+00");
fpctrl.rounding = FloatingPointControl.roundToNearest;
assert(format!"%.0e"(3.5) == "4e+00");
assert(format!"%.0e"(4.5) == "4e+00");
assert(format!"%.0e"(-3.5) == "-4e+00");
assert(format!"%.0e"(-4.5) == "-4e+00");
}
}
@safe pure unittest
{
static assert(format("%e",1.0) == "1.000000e+00");
static assert(format("%e",-1.234e156) == "-1.234000e+156");
static assert(format("%a",1.0) == "0x1p+0");
static assert(format("%a",-1.234e156) == "-0x1.7024c96ca3ce4p+518");
static assert(format("%f",1.0) == "1.000000");
static assert(format("%f",-1.234e156) ==
"-123399999999999990477495546305353609103201879173427886566531" ~
"0740685826234179310516880117527217443004051984432279880308552" ~
"009640198043032289366552939010719744.000000");
static assert(format("%e",1.0f) == "1.000000e+00");
static assert(format("%e",-1.234e23f) == "-1.234000e+23");
static assert(format("%a",1.0f) == "0x1p+0");
static assert(format("%a",-1.234e23f) == "-0x1.a2187p+76");
static assert(format("%f",1.0f) == "1.000000");
static assert(format("%f",-1.234e23f) == "-123399998884238311030784.000000");
}
/*
Formatting a `creal` is deprecated but still kept around for a while.
*/
deprecated("Use of complex types is deprecated. Use std.complex")
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
if (is(immutable T : immutable creal) && !is(T == enum) && !hasToString!(T, Char))
{
immutable creal val = obj;
formatValueImpl(w, val.re, f);
if (val.im >= 0)
{
put(w, '+');
}
formatValueImpl(w, val.im, f);
put(w, 'i');
}
/*
Formatting an `ireal` is deprecated but still kept around for a while.
*/
deprecated("Use of imaginary types is deprecated. Use std.complex")
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
if (is(immutable T : immutable ireal) && !is(T == enum) && !hasToString!(T, Char))
{
immutable ireal val = obj;
formatValueImpl(w, val.im, f);
put(w, 'i');
}
/*
Individual characters are formatted as Unicode characters with `%s`
and as integers with integral-specific format specs
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
{
CharTypeOf!T[1] val = obj;
if (f.spec == 's' || f.spec == 'c')
writeAligned(w, val[], f);
else
{
alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2];
formatValueImpl(w, cast(U) val[0], f);
}
}
@safe pure unittest
{
assertCTFEable!(
{
formatTest( 'c', "c" );
});
}
@system unittest
{
class C1 { char val; alias val this; this(char v){ val = v; } }
class C2 { char val; alias val this; this(char v){ val = v; }
override string toString() const { return "C"; } }
formatTest( new C1('c'), "c" );
formatTest( new C2('c'), "C" );
struct S1 { char val; alias val this; }
struct S2 { char val; alias val this;
string toString() const { return "S"; } }
formatTest( S1('c'), "c" );
formatTest( S2('c'), "S" );
}
@safe unittest
{
//Little Endian
formatTest( "%-r", cast( char)'c', ['c' ] );
formatTest( "%-r", cast(wchar)'c', ['c', 0 ] );
formatTest( "%-r", cast(dchar)'c', ['c', 0, 0, 0] );
formatTest( "%-r", '本', ['\x2c', '\x67'] );
//Big Endian
formatTest( "%+r", cast( char)'c', [ 'c'] );
formatTest( "%+r", cast(wchar)'c', [0, 'c'] );
formatTest( "%+r", cast(dchar)'c', [0, 0, 0, 'c'] );
formatTest( "%+r", '本', ['\x67', '\x2c'] );
}
@safe pure unittest
{
string t1 = format("[%6s] [%-6s]", 'A', 'A');
assert(t1 == "[ A] [A ]");
string t2 = format("[%6s] [%-6s]", '本', '本');
assert(t2 == "[ 本] [本 ]");
}
/*
Strings are formatted like $(REF printf, core, stdc, stdio)
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T obj,
scope const ref FormatSpec!Char f)
if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
{
Unqual!(StringTypeOf!T) val = obj; // for `alias this`, see bug5371
formatRange(w, val, f);
}
@safe unittest
{
formatTest( "abc", "abc" );
}
@system unittest
{
// Test for bug 5371 for classes
class C1 { const string var; alias var this; this(string s){ var = s; } }
class C2 { string var; alias var this; this(string s){ var = s; } }
formatTest( new C1("c1"), "c1" );
formatTest( new C2("c2"), "c2" );
// Test for bug 5371 for structs
struct S1 { const string var; alias var this; }
struct S2 { string var; alias var this; }
formatTest( S1("s1"), "s1" );
formatTest( S2("s2"), "s2" );
}
@system unittest
{
class C3 { string val; alias val this; this(string s){ val = s; }
override string toString() const { return "C"; } }
formatTest( new C3("c3"), "C" );
struct S3 { string val; alias val this;
string toString() const { return "S"; } }
formatTest( S3("s3"), "S" );
}
@safe pure unittest
{
//Little Endian
formatTest( "%-r", "ab"c, ['a' , 'b' ] );
formatTest( "%-r", "ab"w, ['a', 0 , 'b', 0 ] );
formatTest( "%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0] );
formatTest( "%-r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] );
formatTest( "%-r", "日本語"w, ['\xe5', '\x65', '\x2c', '\x67', '\x9e', '\x8a']);
formatTest( "%-r", "日本語"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67',
'\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00'] );
//Big Endian
formatTest( "%+r", "ab"c, [ 'a', 'b'] );
formatTest( "%+r", "ab"w, [ 0, 'a', 0, 'b'] );
formatTest( "%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b'] );
formatTest( "%+r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] );
formatTest( "%+r", "日本語"w, ['\x65', '\xe5', '\x67', '\x2c', '\x8a', '\x9e'] );
formatTest( "%+r", "日本語"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00',
'\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e'] );
}
@safe pure unittest
{
string t1 = format("[%6s] [%-6s]", "AB", "AB");
assert(t1 == "[ AB] [AB ]");
string t2 = format("[%6s] [%-6s]", "本Ä", "本Ä");
assert(t2 == "[ 本Ä] [本Ä ]");
}
/*
Static-size arrays are formatted as dynamic arrays.
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T obj,
scope const ref FormatSpec!Char f)
if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
{
formatValueImpl(w, obj[], f);
}
// Test for https://issues.dlang.org/show_bug.cgi?id=8310
@safe unittest
{
import std.array : appender;
FormatSpec!char f;
auto w = appender!string();
char[2] two = ['a', 'b'];
formatValue(w, two, f);
char[2] getTwo(){ return two; }
formatValue(w, getTwo(), f);
}
/*
Dynamic arrays are formatted as input ranges.
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
{
static if (is(immutable(ArrayTypeOf!T) == immutable(void[])))
{
formatValueImpl(w, cast(const ubyte[]) obj, f);
}
else static if (!isInputRange!T)
{
alias U = Unqual!(ArrayTypeOf!T);
static assert(isInputRange!U, U.stringof ~ " must be an InputRange");
U val = obj;
formatValueImpl(w, val, f);
}
else
{
formatRange(w, obj, f);
}
}
// bug 20848
@safe unittest
{
class C
{
immutable(void)[] data;
}
import std.typecons : Nullable;
Nullable!C c;
}
// alias this, input range I/F, and toString()
@system unittest
{
struct S(int flags)
{
int[] arr;
static if (flags & 1)
alias arr this;
static if (flags & 2)
{
@property bool empty() const { return arr.length == 0; }
@property int front() const { return arr[0] * 2; }
void popFront() { arr = arr[1..$]; }
}
static if (flags & 4)
string toString() const { return "S"; }
}
formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])");
formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628
formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]");
formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]");
formatTest(S!0b100([0, 1, 2]), "S");
formatTest(S!0b101([0, 1, 2]), "S"); // Test for bug 7628
formatTest(S!0b110([0, 1, 2]), "S");
formatTest(S!0b111([0, 1, 2]), "S");
class C(uint flags)
{
int[] arr;
static if (flags & 1)
alias arr this;
this(int[] a) { arr = a; }
static if (flags & 2)
{
@property bool empty() const { return arr.length == 0; }
@property int front() const { return arr[0] * 2; }
void popFront() { arr = arr[1..$]; }
}
static if (flags & 4)
override string toString() const { return "C"; }
}
formatTest(new C!0b000([0, 1, 2]), (new C!0b000([])).toString());
formatTest(new C!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628
formatTest(new C!0b010([0, 1, 2]), "[0, 2, 4]");
formatTest(new C!0b011([0, 1, 2]), "[0, 2, 4]");
formatTest(new C!0b100([0, 1, 2]), "C");
formatTest(new C!0b101([0, 1, 2]), "C"); // Test for bug 7628
formatTest(new C!0b110([0, 1, 2]), "C");
formatTest(new C!0b111([0, 1, 2]), "C");
}
@system unittest
{
// void[]
void[] val0;
formatTest( val0, "[]" );
void[] val = cast(void[]) cast(ubyte[])[1, 2, 3];
formatTest( val, "[1, 2, 3]" );
void[0] sval0 = [];
formatTest( sval0, "[]");
void[3] sval = cast(void[3]) cast(ubyte[3])[1, 2, 3];
formatTest( sval, "[1, 2, 3]" );
}
@safe unittest
{
// const(T[]) -> const(T)[]
const short[] a = [1, 2, 3];
formatTest( a, "[1, 2, 3]" );
struct S { const(int[]) arr; alias arr this; }
auto s = S([1,2,3]);
formatTest( s, "[1, 2, 3]" );
}
// https://issues.dlang.org/show_bug.cgi?id=6640
@safe unittest
{
struct Range
{
@safe:
string value;
@property bool empty() const { return !value.length; }
@property dchar front() const { return value.front; }
void popFront() { value.popFront(); }
@property size_t length() const { return value.length; }
}
immutable table =
[
["[%s]", "[string]"],
["[%10s]", "[ string]"],
["[%-10s]", "[string ]"],
["[%(%02x %)]", "[73 74 72 69 6e 67]"],
["[%(%c %)]", "[s t r i n g]"],
];
foreach (e; table)
{
formatTest(e[0], "string", e[1]);
formatTest(e[0], Range("string"), e[1]);
}
}
@system unittest
{
// string literal from valid UTF sequence is encoding free.
static foreach (StrType; AliasSeq!(string, wstring, dstring))
{
// Valid and printable (ASCII)
formatTest( [cast(StrType)"hello"],
`["hello"]` );
// 1 character escape sequences (' is not escaped in strings)
formatTest( [cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"],
`["\"'\0\\\a\b\f\n\r\t\v"]` );
// 1 character optional escape sequences
formatTest( [cast(StrType)"\'\?"],
`["'?"]` );
// Valid and non-printable code point (<= U+FF)
formatTest( [cast(StrType)"\x10\x1F\x20test"],
`["\x10\x1F test"]` );
// Valid and non-printable code point (<= U+FFFF)
formatTest( [cast(StrType)"\u200B..\u200F"],
`["\u200B..\u200F"]` );
// Valid and non-printable code point (<= U+10FFFF)
formatTest( [cast(StrType)"\U000E0020..\U000E007F"],
`["\U000E0020..\U000E007F"]` );
}
// invalid UTF sequence needs hex-string literal postfix (c/w/d)
{
// U+FFFF with UTF-8 (Invalid code point for interchange)
formatTest( [cast(string)[0xEF, 0xBF, 0xBF]],
`[x"EF BF BF"c]` );
// U+FFFF with UTF-16 (Invalid code point for interchange)
formatTest( [cast(wstring)[0xFFFF]],
`[x"FFFF"w]` );
// U+FFFF with UTF-32 (Invalid code point for interchange)
formatTest( [cast(dstring)[0xFFFF]],
`[x"FFFF"d]` );
}
}
@safe unittest
{
// nested range formatting with array of string
formatTest( "%({%(%02x %)}%| %)", ["test", "msg"],
`{74 65 73 74} {6d 73 67}` );
}
@safe unittest
{
// stop auto escaping inside range formatting
auto arr = ["hello", "world"];
formatTest( "%(%s, %)", arr, `"hello", "world"` );
formatTest( "%-(%s, %)", arr, `hello, world` );
auto aa1 = [1:"hello", 2:"world"];
formatTest( "%(%s:%s, %)", aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`] );
formatTest( "%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`] );
auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]];
formatTest( "%-(%s:%s, %)", aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`] );
formatTest( "%-(%s:%(%s%), %)", aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`] );
formatTest( "%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`] );
}
// input range formatting
private void formatRange(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f)
if (isInputRange!T)
{
// in this mode, we just want to do a representative print to discover if the format spec is valid
enum formatTestMode = is(Writer == NoOpSink);
import std.conv : text;
// Formatting character ranges like string
if (f.spec == 's')
{
alias E = ElementType!T;
static if (!is(E == enum) && is(CharTypeOf!E))
{
static if (is(StringTypeOf!T))
writeAligned(w, val[0 .. f.precision < $ ? f.precision : $], f);
else
{
if (!f.flDash)
{
static if (hasLength!T)
{
// right align
auto len = val.length;
}
else static if (isForwardRange!T && !isInfinite!T)
{
auto len = walkLength(val.save);
}
else
{
enforceFmt(f.width == 0, "Cannot right-align a range without length");
size_t len = 0;
}
if (f.precision != f.UNSPECIFIED && len > f.precision)
len = f.precision;
if (f.width > len)
foreach (i ; 0 .. f.width - len)
put(w, ' ');
if (f.precision == f.UNSPECIFIED)
put(w, val);
else
{
size_t printed = 0;
for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
put(w, val.front);
}
}
else
{
size_t printed = void;
// left align
if (f.precision == f.UNSPECIFIED)
{
static if (hasLength!T)
{
printed = val.length;
put(w, val);
}
else
{
printed = 0;
for (; !val.empty; val.popFront(), ++printed)
{
put(w, val.front);
static if (formatTestMode) break; // one is enough to test
}
}
}
else
{
printed = 0;
for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
put(w, val.front);
}
if (f.width > printed)
foreach (i ; 0 .. f.width - printed)
put(w, ' ');
}
}
}
else
{
put(w, f.seqBefore);
if (!val.empty)
{
formatElement(w, val.front, f);
val.popFront();
for (size_t i; !val.empty; val.popFront(), ++i)
{
put(w, f.seqSeparator);
formatElement(w, val.front, f);
static if (formatTestMode) break; // one is enough to test
}
}
static if (!isInfinite!T) put(w, f.seqAfter);
}
}
else if (f.spec == 'r')
{
static if (is(DynamicArrayTypeOf!T))
{
alias ARR = DynamicArrayTypeOf!T;
scope a = cast(ARR) val;
foreach (e ; a)
{
formatValue(w, e, f);
static if (formatTestMode) break; // one is enough to test
}
}
else
{
for (size_t i; !val.empty; val.popFront(), ++i)
{
formatValue(w, val.front, f);
static if (formatTestMode) break; // one is enough to test
}
}
}
else if (f.spec == '(')
{
if (val.empty)
return;
// Nested specifier is to be used
for (;;)
{
auto fmt = FormatSpec!Char(f.nested);
w: while (true)
{
immutable r = fmt.writeUpToNextSpec(w);
// There was no format specifier, so break
if (!r)
break;
if (f.flDash)
formatValue(w, val.front, fmt);
else
formatElement(w, val.front, fmt);
// Check if there will be a format specifier farther on in the
// string. If so, continue the loop, otherwise break. This
// prevents extra copies of the `sep` from showing up.
foreach (size_t i; 0 .. fmt.trailing.length)
if (fmt.trailing[i] == '%')
continue w;
break w;
}
static if (formatTestMode)
{
break; // one is enough to test
}
else
{
if (f.sep !is null)
{
put(w, fmt.trailing);
val.popFront();
if (val.empty)
break;
put(w, f.sep);
}
else
{
val.popFront();
if (val.empty)
break;
put(w, fmt.trailing);
}
}
}
}
else
throw new FormatException(text("Incorrect format specifier for range: %", f.spec));
}
// https://issues.dlang.org/show_bug.cgi?id=18778
@safe pure unittest
{
assert(format("%-(%1$s - %1$s, %)", ["A", "B", "C"]) == "A - A, B - B, C - C");
}
@safe pure unittest
{
assert(collectExceptionMsg(format("%d", "hi")).back == 'd');
}
// character formatting with ecaping
package(std.format) void formatChar(Writer)(ref Writer w, in dchar c, in char quote)
{
import std.uni : isGraphical;
string fmt;
if (isGraphical(c))
{
if (c == quote || c == '\\')
put(w, '\\');
put(w, c);
return;
}
else if (c <= 0xFF)
{
if (c < 0x20)
{
foreach (i, k; "\n\r\t\a\b\f\v\0")
{
if (c == k)
{
put(w, '\\');
put(w, "nrtabfv0"[i]);
return;
}
}
}
fmt = "\\x%02X";
}
else if (c <= 0xFFFF)
fmt = "\\u%04X";
else
fmt = "\\U%08X";
formattedWrite(w, fmt, cast(uint) c);
}
/*
Associative arrays are formatted by using `':'` and $(D ", ") as
separators, and enclosed by `'['` and `']'`.
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
{
AssocArrayTypeOf!T val = obj;
const spec = f.spec;
enforceFmt(spec == 's' || spec == '(',
"incompatible format character for associative array argument: %" ~ spec);
enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator;
auto fmtSpec = spec == '(' ? f.nested : defSpec;
size_t i = 0;
immutable end = val.length;
if (spec == 's')
put(w, f.seqBefore);
foreach (k, ref v; val)
{
auto fmt = FormatSpec!Char(fmtSpec);
fmt.writeUpToNextSpec(w);
if (f.flDash)
{
formatValue(w, k, fmt);
fmt.writeUpToNextSpec(w);
formatValue(w, v, fmt);
}
else
{
formatElement(w, k, fmt);
fmt.writeUpToNextSpec(w);
formatElement(w, v, fmt);
}
if (f.sep !is null)
{
fmt.writeUpToNextSpec(w);
if (++i != end)
put(w, f.sep);
}
else
{
if (++i != end)
fmt.writeUpToNextSpec(w);
}
}
if (spec == 's')
put(w, f.seqAfter);
}
@safe unittest
{
// Bug #17269. Behavior similar to `struct A { Nullable!string B; }`
struct StringAliasThis
{
@property string value() const { assert(0); }
alias value this;
string toString() { return "helloworld"; }
private string _value;
}
struct TestContainer
{
StringAliasThis testVar;
}
import std.array : appender;
auto w = appender!string();
auto spec = singleSpec("%s");
formatElement(w, TestContainer(), spec);
assert(w.data == "TestContainer(helloworld)", w.data);
}
// https://issues.dlang.org/show_bug.cgi?id=17269
@safe unittest
{
import std.typecons : Nullable;
struct Foo
{
Nullable!string bar;
}
Foo f;
formatTest(f, "Foo(Nullable.null)");
}
@safe unittest
{
assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd');
int[string] aa0;
formatTest( aa0, `[]` );
// elements escaping
formatTest( ["aaa":1, "bbb":2],
[`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`] );
formatTest( ['c':"str"],
`['c':"str"]` );
formatTest( ['"':"\"", '\'':"'"],
[`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`] );
// range formatting for AA
auto aa3 = [1:"hello", 2:"world"];
// escape
formatTest( "{%(%s:%s $ %)}", aa3,
[`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]);
// use range formatting for key and value, and use %|
formatTest( "{%([%04d->%(%c.%)]%| $ %)}", aa3,
[`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`, `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`] );
// https://issues.dlang.org/show_bug.cgi?id=12135
formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>");
formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>");
}
@system unittest
{
class C1 { int[char] val; alias val this; this(int[char] v){ val = v; } }
class C2 { int[char] val; alias val this; this(int[char] v){ val = v; }
override string toString() const { return "C"; } }
formatTest( new C1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] );
formatTest( new C2(['c':1, 'd':2]), "C" );
struct S1 { int[char] val; alias val this; }
struct S2 { int[char] val; alias val this;
string toString() const { return "S"; } }
formatTest( S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] );
formatTest( S2(['c':1, 'd':2]), "S" );
}
// https://issues.dlang.org/show_bug.cgi?id=8921
@safe unittest
{
enum E : char { A = 'a', B = 'b', C = 'c' }
E[3] e = [E.A, E.B, E.C];
formatTest(e, "[A, B, C]");
E[] e2 = [E.A, E.B, E.C];
formatTest(e2, "[A, B, C]");
}
package(std.format) enum HasToStringResult
{
none,
hasSomeToString,
constCharSink,
constCharSinkFormatString,
constCharSinkFormatSpec,
customPutWriter,
customPutWriterFormatSpec,
}
package(std.format) template hasToString(T, Char)
{
static if (isPointer!T)
{
// X* does not have toString, even if X is aggregate type has toString.
enum hasToString = HasToStringResult.none;
}
else static if (is(typeof(
{T val = void;
const FormatSpec!Char f;
static struct S {void put(scope Char s){}}
S s;
val.toString(s, f);
// force toString to take parameters by ref
static assert(!__traits(compiles, val.toString(s, FormatSpec!Char())));
static assert(!__traits(compiles, val.toString(S(), f)));}
)))
{
enum hasToString = HasToStringResult.customPutWriterFormatSpec;
}
else static if (is(typeof(
{T val = void;
static struct S {void put(scope Char s){}}
S s;
val.toString(s);
// force toString to take parameters by ref
static assert(!__traits(compiles, val.toString(S())));}
)))
{
enum hasToString = HasToStringResult.customPutWriter;
}
else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((scope const(char)[] s){}, f); })))
{
enum hasToString = HasToStringResult.constCharSinkFormatSpec;
}
else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}, "%s"); })))
{
enum hasToString = HasToStringResult.constCharSinkFormatString;
}
else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}); })))
{
enum hasToString = HasToStringResult.constCharSink;
}
else static if (is(typeof({ T val = void; return val.toString(); }()) S) && isSomeString!S)
{
enum hasToString = HasToStringResult.hasSomeToString;
}
else
{
enum hasToString = HasToStringResult.none;
}
}
@safe unittest
{
static struct A
{
void toString(Writer)(ref Writer w)
if (isOutputRange!(Writer, string))
{}
}
static struct B
{
void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) {}
}
static struct C
{
void toString(scope void delegate(scope const(char)[]) sink, string fmt) {}
}
static struct D
{
void toString(scope void delegate(scope const(char)[]) sink) {}
}
static struct E
{
string toString() {return "";}
}
static struct F
{
void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
if (isOutputRange!(Writer, string))
{}
}
static struct G
{
string toString() {return "";}
void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
}
static struct H
{
string toString() {return "";}
void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
if (isOutputRange!(Writer, string))
{}
}
static struct I
{
void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
if (isOutputRange!(Writer, string))
{}
}
static struct J
{
string toString() {return "";}
void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt)
if (isOutputRange!(Writer, string))
{}
}
static struct K
{
void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt)
if (isOutputRange!(Writer, string))
{}
}
static struct L
{
void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt)
if (isOutputRange!(Writer, string))
{}
}
with(HasToStringResult)
{
static assert(hasToString!(A, char) == customPutWriter);
static assert(hasToString!(B, char) == constCharSinkFormatSpec);
static assert(hasToString!(C, char) == constCharSinkFormatString);
static assert(hasToString!(D, char) == constCharSink);
static assert(hasToString!(E, char) == hasSomeToString);
static assert(hasToString!(F, char) == customPutWriterFormatSpec);
static assert(hasToString!(G, char) == customPutWriter);
static assert(hasToString!(H, char) == customPutWriterFormatSpec);
static assert(hasToString!(I, char) == customPutWriterFormatSpec);
static assert(hasToString!(J, char) == hasSomeToString);
static assert(hasToString!(K, char) == constCharSinkFormatSpec);
static assert(hasToString!(L, char) == none);
}
}
// object formatting with toString
private void formatObject(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f)
if (hasToString!(T, Char))
{
enum overload = hasToString!(T, Char);
enum noop = is(Writer == NoOpSink);
static if (overload == HasToStringResult.customPutWriterFormatSpec)
{
static if (!noop) val.toString(w, f);
}
else static if (overload == HasToStringResult.customPutWriter)
{
static if (!noop) val.toString(w);
}
else static if (overload == HasToStringResult.constCharSinkFormatSpec)
{
static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f);
}
else static if (overload == HasToStringResult.constCharSinkFormatString)
{
static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f.getCurFmtStr());
}
else static if (overload == HasToStringResult.constCharSink)
{
static if (!noop) val.toString((scope const(char)[] s) { put(w, s); });
}
else static if (overload == HasToStringResult.hasSomeToString)
{
static if (!noop) put(w, val.toString());
}
else
{
static assert(0, "No way found to format " ~ T.stringof ~ " as string");
}
}
@system unittest
{
static interface IF1 { }
class CIF1 : IF1 { }
static struct SF1 { }
static union UF1 { }
static class CF1 { }
static interface IF2 { string toString(); }
static class CIF2 : IF2 { override string toString() { return ""; } }
static struct SF2 { string toString() { return ""; } }
static union UF2 { string toString() { return ""; } }
static class CF2 { override string toString() { return ""; } }
static interface IK1 { void toString(scope void delegate(scope const(char)[]) sink,
FormatSpec!char) const; }
static class CIK1 : IK1 { override void toString(scope void delegate(scope const(char)[]) sink,
FormatSpec!char) const { sink("CIK1"); } }
static struct KS1 { void toString(scope void delegate(scope const(char)[]) sink,
FormatSpec!char) const { sink("KS1"); } }
static union KU1 { void toString(scope void delegate(scope const(char)[]) sink,
FormatSpec!char) const { sink("KU1"); } }
static class KC1 { void toString(scope void delegate(scope const(char)[]) sink,
FormatSpec!char) const { sink("KC1"); } }
IF1 cif1 = new CIF1;
assertThrown!FormatException(format("%f", cif1));
assertThrown!FormatException(format("%f", SF1()));
assertThrown!FormatException(format("%f", UF1()));
assertThrown!FormatException(format("%f", new CF1()));
IF2 cif2 = new CIF2;
assertThrown!FormatException(format("%f", cif2));
assertThrown!FormatException(format("%f", SF2()));
assertThrown!FormatException(format("%f", UF2()));
assertThrown!FormatException(format("%f", new CF2()));
IK1 cik1 = new CIK1;
assert(format("%f", cik1) == "CIK1");
assert(format("%f", KS1()) == "KS1");
assert(format("%f", KU1()) == "KU1");
assert(format("%f", new KC1()) == "KC1");
}
/*
Aggregates
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
if (is(T == class) && !is(T == enum))
{
enforceValidFormatSpec!(T, Char)(f);
// TODO: remove this check once `@disable override` deprecation cycle is finished
static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
static assert(!__traits(isDisabled, T.toString), T.stringof ~
" cannot be formatted because its `toString` is marked with `@disable`");
if (val is null)
put(w, "null");
else
{
import std.algorithm.comparison : among;
enum overload = hasToString!(T, Char);
with(HasToStringResult)
static if ((is(T == immutable) || is(T == const) || is(T == shared)) && overload == none)
{
// Remove this when Object gets const toString
// https://issues.dlang.org/show_bug.cgi?id=7879
static if (is(T == immutable))
put(w, "immutable(");
else static if (is(T == const))
put(w, "const(");
else static if (is(T == shared))
put(w, "shared(");
put(w, typeid(Unqual!T).name);
put(w, ')');
}
else static if (overload.among(constCharSink, constCharSinkFormatString, constCharSinkFormatSpec) ||
(!isInputRange!T && !is(BuiltinTypeOf!T)))
{
formatObject!(Writer, T, Char)(w, val, f);
}
else
{
//string delegate() dg = &val.toString;
Object o = val; // workaround
string delegate() dg = &o.toString;
scope Object object = new Object();
if (dg.funcptr != (&object.toString).funcptr) // toString is overridden
{
formatObject(w, val, f);
}
else static if (isInputRange!T)
{
formatRange(w, val, f);
}
else static if (is(BuiltinTypeOf!T X))
{
X x = val;
formatValueImpl(w, x, f);
}
else
{
formatObject(w, val, f);
}
}
}
}
@system unittest
{
import std.array : appender;
import std.range.interfaces;
// class range (https://issues.dlang.org/show_bug.cgi?id=5154)
auto c = inputRangeObject([1,2,3,4]);
formatTest( c, "[1, 2, 3, 4]" );
assert(c.empty);
c = null;
formatTest( c, "null" );
}
@system unittest
{
// https://issues.dlang.org/show_bug.cgi?id=5354
// If the class has both range I/F and custom toString, the use of custom
// toString routine is prioritized.
// Enable the use of custom toString that gets a sink delegate
// for class formatting.
enum inputRangeCode =
q{
int[] arr;
this(int[] a){ arr = a; }
@property int front() const { return arr[0]; }
@property bool empty() const { return arr.length == 0; }
void popFront(){ arr = arr[1..$]; }
};
class C1
{
mixin(inputRangeCode);
void toString(scope void delegate(scope const(char)[]) dg,
scope const ref FormatSpec!char f) const
{
dg("[012]");
}
}
class C2
{
mixin(inputRangeCode);
void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); }
}
class C3
{
mixin(inputRangeCode);
void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); }
}
class C4
{
mixin(inputRangeCode);
override string toString() const { return "[012]"; }
}
class C5
{
mixin(inputRangeCode);
}
formatTest( new C1([0, 1, 2]), "[012]" );
formatTest( new C2([0, 1, 2]), "[012]" );
formatTest( new C3([0, 1, 2]), "[012]" );
formatTest( new C4([0, 1, 2]), "[012]" );
formatTest( new C5([0, 1, 2]), "[0, 1, 2]" );
}
// outside the unittest block, otherwise the FQN of the
// class contains the line number of the unittest
version (StdUnittest)
{
private class C {}
}
// https://issues.dlang.org/show_bug.cgi?id=7879
@safe unittest
{
const(C) c;
auto s = format("%s", c);
assert(s == "null");
immutable(C) c2 = new C();
s = format("%s", c2);
assert(s == "immutable(std.format.internal.write.C)");
const(C) c3 = new C();
s = format("%s", c3);
assert(s == "const(std.format.internal.write.C)");
shared(C) c4 = new C();
s = format("%s", c4);
assert(s == "shared(std.format.internal.write.C)");
}
// https://issues.dlang.org/show_bug.cgi?id=19003
@safe unittest
{
struct S
{
int i;
@disable this();
invariant { assert(this.i); }
this(int i) @safe in { assert(i); } do { this.i = i; }
string toString() { return "S"; }
}
S s = S(1);
format!"%s"(s);
}
// https://issues.dlang.org/show_bug.cgi?id=20218
@safe pure unittest
{
void notCalled()
{
import std.range : repeat;
auto value = 1.repeat;
// test that range is not evaluated to completion at compiletime
format!"%s"(value);
}
}
// https://issues.dlang.org/show_bug.cgi?id=7879
@safe unittest
{
class F
{
override string toString() const @safe
{
return "Foo";
}
}
const(F) c;
auto s = format("%s", c);
assert(s == "null");
const(F) c2 = new F();
s = format("%s", c2);
assert(s == "Foo", s);
}
// ditto
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
{
enforceValidFormatSpec!(T, Char)(f);
if (val is null)
put(w, "null");
else
{
static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
static assert(!__traits(isDisabled, T.toString), T.stringof ~
" cannot be formatted because its `toString` is marked with `@disable`");
static if (hasToString!(T, Char) != HasToStringResult.none)
{
formatObject(w, val, f);
}
else static if (isInputRange!T)
{
formatRange(w, val, f);
}
else
{
version (Windows)
{
import core.sys.windows.com : IUnknown;
static if (is(T : IUnknown))
{
formatValueImpl(w, *cast(void**)&val, f);
}
else
{
formatValueImpl(w, cast(Object) val, f);
}
}
else
{
formatValueImpl(w, cast(Object) val, f);
}
}
}
}
@system unittest
{
// interface
import std.range.interfaces;
InputRange!int i = inputRangeObject([1,2,3,4]);
formatTest( i, "[1, 2, 3, 4]" );
assert(i.empty);
i = null;
formatTest( i, "null" );
// interface (downcast to Object)
interface Whatever {}
class C : Whatever
{
override @property string toString() const { return "ab"; }
}
Whatever val = new C;
formatTest( val, "ab" );
// https://issues.dlang.org/show_bug.cgi?id=11175
version (Windows)
{
import core.sys.windows.com : IUnknown, IID;
import core.sys.windows.windef : HRESULT;
interface IUnknown2 : IUnknown { }
class D : IUnknown2
{
extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; }
extern(Windows) uint AddRef() { return 0; }
extern(Windows) uint Release() { return 0; }
}
IUnknown2 d = new D;
string expected = format("%X", cast(void*) d);
formatTest(d, expected);
}
}
/// ditto
// Maybe T is noncopyable struct, so receive it by 'auto ref'.
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T val,
scope const ref FormatSpec!Char f)
if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
{
static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
static assert(!__traits(isDisabled, T.toString), T.stringof ~
" cannot be formatted because its `toString` is marked with `@disable`");
enforceValidFormatSpec!(T, Char)(f);
static if (hasToString!(T, Char))
{
formatObject(w, val, f);
}
else static if (isInputRange!T)
{
formatRange(w, val, f);
}
else static if (is(T == struct))
{
enum left = T.stringof~"(";
enum separator = ", ";
enum right = ")";
put(w, left);
foreach (i, e; val.tupleof)
{
static if (__traits(identifier, val.tupleof[i]) == "this")
continue;
else static if (0 < i && val.tupleof[i-1].offsetof == val.tupleof[i].offsetof)
{
static if (i == val.tupleof.length - 1 || val.tupleof[i].offsetof != val.tupleof[i+1].offsetof)
put(w, separator~val.tupleof[i].stringof[4..$]~"}");
else
put(w, separator~val.tupleof[i].stringof[4..$]);
}
else static if (i+1 < val.tupleof.length && val.tupleof[i].offsetof == val.tupleof[i+1].offsetof)
put(w, (i > 0 ? separator : "")~"#{overlap "~val.tupleof[i].stringof[4..$]);
else
{
static if (i > 0)
put(w, separator);
formatElement(w, e, f);
}
}
put(w, right);
}
else
{
put(w, T.stringof);
}
}
// https://issues.dlang.org/show_bug.cgi?id=9588
@safe pure unittest
{
struct S { int x; bool empty() { return false; } }
formatTest( S(), "S(0)" );
}
// https://issues.dlang.org/show_bug.cgi?id=4638
@safe unittest
{
struct U8 { string toString() const { return "blah"; } }
struct U16 { wstring toString() const { return "blah"; } }
struct U32 { dstring toString() const { return "blah"; } }
formatTest( U8(), "blah" );
formatTest( U16(), "blah" );
formatTest( U32(), "blah" );
}
// https://issues.dlang.org/show_bug.cgi?id=3890
@safe unittest
{
struct Int{ int n; }
struct Pair{ string s; Int i; }
formatTest( Pair("hello", Int(5)),
`Pair("hello", Int(5))` );
}
@system unittest
{
// union formatting without toString
union U1
{
int n;
string s;
}
U1 u1;
formatTest( u1, "U1" );
// union formatting with toString
union U2
{
int n;
string s;
string toString() const { return s; }
}
U2 u2;
u2.s = "hello";
formatTest( u2, "hello" );
}
@system unittest
{
import std.array;
// https://issues.dlang.org/show_bug.cgi?id=7230
static struct Bug7230
{
string s = "hello";
union {
string a;
int b;
double c;
}
long x = 10;
}
Bug7230 bug;
bug.b = 123;
FormatSpec!char f;
auto w = appender!(char[])();
formatValue(w, bug, f);
assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`);
}
@safe unittest
{
import std.array : appender;
static struct S{ @disable this(this); }
S s;
FormatSpec!char f;
auto w = appender!string();
formatValue(w, s, f);
assert(w.data == "S()");
}
@safe unittest
{
//struct Foo { @disable string toString(); }
//Foo foo;
interface Bar { @disable string toString(); }
Bar bar;
import std.array : appender;
auto w = appender!(char[])();
FormatSpec!char f;
// NOTE: structs cant be tested : the assertion is correct so compilation
// continues and fails when trying to link the unimplemented toString.
//static assert(!__traits(compiles, formatValue(w, foo, f)));
static assert(!__traits(compiles, formatValue(w, bar, f)));
}
/*
`enum`s are formatted like their base value
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
if (is(T == enum))
{
if (f.spec == 's')
{
foreach (i, e; EnumMembers!T)
{
if (val == e)
{
formatValueImpl(w, __traits(allMembers, T)[i], f);
return;
}
}
import std.array : appender;
auto w2 = appender!string();
// val is not a member of T, output cast(T) rawValue instead.
put(w2, "cast(" ~ T.stringof ~ ")");
static assert(!is(OriginalType!T == T), "OriginalType!" ~ T.stringof ~
"must not be equal to " ~ T.stringof);
FormatSpec!Char f2 = f;
f2.width = 0;
formatValueImpl(w2, cast(OriginalType!T) val, f2);
writeAligned(w, w2.data, f);
return;
}
formatValueImpl(w, cast(OriginalType!T) val, f);
}
@safe unittest
{
enum A { first, second, third }
formatTest( A.second, "second" );
formatTest( cast(A) 72, "cast(A)72" );
}
@safe unittest
{
enum A : string { one = "uno", two = "dos", three = "tres" }
formatTest( A.three, "three" );
formatTest( cast(A)"mill\&oacute;n", "cast(A)mill\&oacute;n" );
}
@safe unittest
{
enum A : bool { no, yes }
formatTest( A.yes, "yes" );
formatTest( A.no, "no" );
}
@safe unittest
{
// Test for bug 6892
enum Foo { A = 10 }
formatTest("%s", Foo.A, "A");
formatTest(">%4s<", Foo.A, "> A<");
formatTest("%04d", Foo.A, "0010");
formatTest("%+2u", Foo.A, "+10");
formatTest("%02x", Foo.A, "0a");
formatTest("%3o", Foo.A, " 12");
formatTest("%b", Foo.A, "1010");
}
@safe pure unittest
{
enum A { one, two, three }
string t1 = format("[%6s] [%-6s]", A.one, A.one);
assert(t1 == "[ one] [one ]");
string t2 = format("[%10s] [%-10s]", cast(A) 10, cast(A) 10);
assert(t2 == "[ cast(A)" ~ "10] [cast(A)" ~ "10 ]"); // due to bug in style checker
}
/*
Pointers are formatted as hex integers.
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T val,
scope const ref FormatSpec!Char f)
if (isPointer!T && !is(T == enum) && !hasToString!(T, Char))
{
static if (is(typeof({ shared const void* p = val; })))
alias SharedOf(T) = shared(T);
else
alias SharedOf(T) = T;
const SharedOf!(void*) p = val;
const pnum = ()@trusted{ return cast(ulong) p; }();
if (f.spec == 's')
{
if (p is null)
{
writeAligned(w, "null", f);
return;
}
FormatSpec!Char fs = f; // fs is copy for change its values.
fs.spec = 'X';
formatValueImpl(w, pnum, fs);
}
else
{
enforceFmt(f.spec == 'X' || f.spec == 'x',
"Expected one of %s, %x or %X for pointer type.");
formatValueImpl(w, pnum, f);
}
}
@safe pure unittest
{
int* p;
string t1 = format("[%6s] [%-6s]", p, p);
assert(t1 == "[ null] [null ]");
}
/*
SIMD vectors are formatted as arrays.
*/
package(std.format) void formatValueImpl(Writer, V, Char)(auto ref Writer w, V val, scope const ref FormatSpec!Char f)
if (isSIMDVector!V)
{
formatValueImpl(w, val.array, f);
}
@safe unittest
{
import core.simd;
static if (is(float4))
{
version (X86)
{
version (OSX) {/* https://issues.dlang.org/show_bug.cgi?id=17823 */}
}
else
{
float4 f;
f.array[0] = 1;
f.array[1] = 2;
f.array[2] = 3;
f.array[3] = 4;
formatTest(f, "[1, 2, 3, 4]");
}
}
}
@safe pure unittest
{
int* p = null;
formatTest( p, "null" );
auto q = ()@trusted{ return cast(void*) 0xFFEECCAA; }();
formatTest( q, "FFEECCAA" );
}
// https://issues.dlang.org/show_bug.cgi?id=11782
@safe pure unittest
{
import std.range : iota;
auto a = iota(0, 10);
auto b = iota(0, 10);
auto p = ()@trusted{ auto p = &a; return p; }();
assert(format("%s",p) != format("%s",b));
}
@system pure unittest
{
// Test for https://issues.dlang.org/show_bug.cgi?id=7869
struct S
{
string toString() const { return ""; }
}
S* p = null;
formatTest( p, "null" );
S* q = cast(S*) 0xFFEECCAA;
formatTest( q, "FFEECCAA" );
}
// https://issues.dlang.org/show_bug.cgi?id=8186
@system unittest
{
class B
{
int*a;
this(){ a = new int; }
alias a this;
}
formatTest( B.init, "null" );
}
// https://issues.dlang.org/show_bug.cgi?id=9336
@system pure unittest
{
shared int i;
format("%s", &i);
}
// https://issues.dlang.org/show_bug.cgi?id=11778
@system pure unittest
{
int* p = null;
assertThrown!FormatException(format("%d", p));
assertThrown!FormatException(format("%04d", p + 2));
}
// https://issues.dlang.org/show_bug.cgi?id=12505
@safe pure unittest
{
void* p = null;
formatTest( "%08X", p, "00000000" );
}
/*
Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes`
Known bug: Because of issue https://issues.dlang.org/show_bug.cgi?id=18269
the FunctionAttributes might be wrong.
*/
package(std.format) void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T, scope const ref FormatSpec!Char f)
if (isDelegate!T)
{
formatValueImpl(w, T.stringof, f);
}
@safe unittest
{
void func() @system { __gshared int x; ++x; throw new Exception("msg"); }
version (linux)
{
import std.array : appender;
FormatSpec!char f;
auto w = appender!string();
formatValue(w, &func, f);
assert(w.data.length >= 15 && w.data[0 .. 15] == "void delegate()");
}
}
@safe pure unittest
{
int[] a = [ 1, 3, 2 ];
formatTest( "testing %(%s & %) embedded", a,
"testing 1 & 3 & 2 embedded");
formatTest( "testing %((%s) %)) wyda3", a,
"testing (1) (3) (2) wyda3" );
int[0] empt = [];
formatTest( "(%s)", empt,
"([])" );
}
// Fix for https://issues.dlang.org/show_bug.cgi?id=1591
package(std.format) int getNthInt(string kind, A...)(uint index, A args)
{
return getNth!(kind, isIntegral,int)(index, args);
}
package(std.format) T getNth(string kind, alias Condition, T, A...)(uint index, A args)
{
import std.conv : text, to;
switch (index)
{
foreach (n, _; A)
{
case n:
static if (Condition!(typeof(args[n])))
{
return to!T(args[n]);
}
else
{
throw new FormatException(
text(kind, " expected, not ", typeof(args[n]).stringof,
" for argument #", index + 1));
}
}
default:
throw new FormatException(
text("Missing ", kind, " argument"));
}
}
private bool needToSwapEndianess(Char)(scope const ref FormatSpec!Char f)
{
import std.system : endian, Endian;
return endian == Endian.littleEndian && f.flPlus
|| endian == Endian.bigEndian && f.flDash;
}
private void writeAligned(Writer, T, Char)(auto ref Writer w, T s, scope const ref FormatSpec!Char f)
if (isSomeString!T)
{
size_t width;
if (f.width > 0)
{
// check for non-ascii character
import std.algorithm.searching : any;
if (s.any!(a => a > 0x7F))
{
//TODO: optimize this
import std.uni : graphemeStride;
for (size_t i; i < s.length; i += graphemeStride(s, i))
++width;
}
else
width = s.length;
}
else
width = s.length;
if (!f.flDash)
{
// right align
if (f.width > width)
foreach (i ; 0 .. f.width - width) put(w, ' ');
put(w, s);
}
else
{
// left align
put(w, s);
if (f.width > width)
foreach (i ; 0 .. f.width - width) put(w, ' ');
}
}
@safe pure unittest
{
import std.array : appender;
auto w = appender!string();
auto spec = singleSpec("%s");
writeAligned(w, "a本Ä", spec);
assert(w.data == "a本Ä", w.data);
}
@safe pure unittest
{
import std.array : appender;
auto w = appender!string();
auto spec = singleSpec("%10s");
writeAligned(w, "a本Ä", spec);
assert(w.data == " a本Ä", "|" ~ w.data ~ "|");
}
@safe pure unittest
{
import std.array : appender;
auto w = appender!string();
auto spec = singleSpec("%-10s");
writeAligned(w, "a本Ä", spec);
assert(w.data == "a本Ä ", w.data);
}