format.sformat: Support range-based toString()

This commit is contained in:
Eugen Wissner 2019-02-19 06:39:39 +01:00
parent 5a134ce768
commit 87ba58098e

View File

@ -17,11 +17,19 @@
* To escape `{` or `}`, use `{{` and `}}` respectively. `{{` will be outputted * To escape `{` or `}`, use `{{` and `}}` respectively. `{{` will be outputted
* as a single `{`, `}}` - as a single `}`. * as a single `{`, `}}` - as a single `}`.
* *
* If a custom data type (like $(D_KEYWORD struct) or $(D_KEYWORD class)) * To define the string representation for a custom data type (like
* defines a `stringify()` function that is callable without arguments and * $(D_KEYWORD class) or $(D_KEYWORD struct)), `toString()`-function can be
* returns a $(D_PSYMBOL String), this function is used to produce a string * implemented for that type. `toString()` should be $(D_KEYWORD const) and
* representation for the value. String conversions for the most built-in * accept exactly one argument: an output range for `const(char)[]`. It should
* data types a also available. * return the same output range, advanced after putting the corresponding value
* into it. That is `toString()` signature should look like:
*
* ---
* OR toString(OR)(OR range) const
* if (isOutputRange!(OR, const(char)[]));
* ---
*
* String conversions for the most built-in data types a also available.
* *
* $(D_KEYWORD char), $(D_KEYWORD wchar) and $(D_KEYWORD dchar) ranges are * $(D_KEYWORD char), $(D_KEYWORD wchar) and $(D_KEYWORD dchar) ranges are
* outputted as plain strings (without any delimiters between their elements). * outputted as plain strings (without any delimiters between their elements).
@ -2271,6 +2279,8 @@ private void printToString(string fmt, OR, Args...)(ref OR result,
} }
else static if (is(Unqual!(typeof(args[0].stringify())) == String)) else static if (is(Unqual!(typeof(args[0].stringify())) == String))
{ {
pragma(msg, ".stringify() is deprecated. Use toString() with an output"
~ " range instead");
static if (is(Arg == class) || is(Arg == interface)) static if (is(Arg == class) || is(Arg == interface))
{ {
if (args[0] is null) if (args[0] is null)
@ -2287,6 +2297,24 @@ private void printToString(string fmt, OR, Args...)(ref OR result,
put(result, args[0].stringify()[]); put(result, args[0].stringify()[]);
} }
} }
else static if (is(typeof(args[0].toString(result)) == OR))
{
static if (is(Arg == class) || is(Arg == interface))
{
if (args[0] is null)
{
put(result, "null");
}
else
{
result = args[0].toString(result);
}
}
else
{
result = args[0].toString(result);
}
}
else static if (is(Arg == class)) else static if (is(Arg == class))
{ {
put(result, args[0] is null ? "null" : args[0].toString()); put(result, args[0] is null ? "null" : args[0].toString());
@ -2365,7 +2393,7 @@ String format(string fmt, Args...)(auto ref Args args)
* *
* Returns: $(D_PARAM output). * Returns: $(D_PARAM output).
*/ */
R sformat(string fmt, R, Args...)(return R output, auto ref Args args) R sformat(string fmt, R, Args...)(R output, auto ref Args args)
if (isOutputRange!(R, const(char)[])) if (isOutputRange!(R, const(char)[]))
{ {
alias Specs = ParseFmt!fmt; alias Specs = ParseFmt!fmt;
@ -2523,14 +2551,15 @@ if (isOutputRange!(R, const(char)[]))
} }
assert(format!"{}"(Nested()) == "Nested(0)"); assert(format!"{}"(Nested()) == "Nested(0)");
static struct WithStringify static struct WithToString
{ {
String stringify() const @nogc nothrow pure @safe OR toString(OR)(OR range) const
{ {
return String("stringify method"); put(range, "toString method");
return range;
} }
} }
assert(format!"{}"(WithStringify()) == "stringify method"); assert(format!"{}"(WithToString()) == "toString method");
} }
// Aggregate types. // Aggregate types.
@ -2552,9 +2581,10 @@ if (isOutputRange!(R, const(char)[]))
class B class B
{ {
String stringify() @nogc nothrow pure @safe OR toString(OR)(OR range) const
{ {
return String("Class B"); put(range, "Class B");
return range;
} }
} }
assert(format!"{}"(cast(B) null) == "null"); assert(format!"{}"(cast(B) null) == "null");