Mercurial > projects > ldc
diff tango/tango/text/convert/Layout.d @ 132:1700239cab2e trunk
[svn r136] MAJOR UNSTABLE UPDATE!!!
Initial commit after moving to Tango instead of Phobos.
Lots of bugfixes...
This build is not suitable for most things.
author | lindquist |
---|---|
date | Fri, 11 Jan 2008 17:57:40 +0100 |
parents | |
children | a27941d00351 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tango/tango/text/convert/Layout.d Fri Jan 11 17:57:40 2008 +0100 @@ -0,0 +1,920 @@ +/******************************************************************************* + + copyright: Copyright (c) 2005 Kris. All rights reserved + + license: BSD style: $(LICENSE) + + version: Initial release: 2005 + + author: Kris + + This module provides a general-purpose formatting system to + convert values to text suitable for display. There is support + for alignment, justification, and common format specifiers for + numbers. + + Layout can be customized via configuring various handlers and + associated meta-date. This is utilized to plug in text.locale + for handling custom formats, date/time and culture-specific + conversions. + + The format notation is influenced by that used by the .NET + and ICU frameworks, rather than C-style printf or D-style + writef notation. + +******************************************************************************/ + +module tango.text.convert.Layout; + +private import tango.core.Exception; + +private import Unicode = tango.text.convert.Utf; + +private import Float = tango.text.convert.Float, + Integer = tango.text.convert.Integer; + +/******************************************************************************* + + Platform issues ... + +*******************************************************************************/ + +version (DigitalMars) + { + alias void* Arg; + alias void* ArgList; + } + else + version (X86_64) + { + private import std.stdarg; + alias void* Arg; + alias va_list ArgList; + } + else + { + alias char* Arg; + alias char* ArgList; + } + +/******************************************************************************* + + Contains methods for replacing format items in a string with string + equivalents of each argument. + +*******************************************************************************/ + +class Layout(T) +{ + public alias convert opCall; + public alias uint delegate (T[]) Sink; + + /********************************************************************** + + **********************************************************************/ + + public final T[] sprint (T[] result, T[] formatStr, ...) + { + return vprint (result, formatStr, _arguments, _argptr); + } + + /********************************************************************** + + **********************************************************************/ + + public final T[] vprint (T[] result, T[] formatStr, TypeInfo[] arguments, ArgList args) + { + T* p = result.ptr; + + uint sink (T[] s) + { + int len = s.length; + if (len < (result.ptr + result.length) - p) + { + p [0..len] = s; + p += len; + } + else + error ("Layout.vprint :: output buffer is full"); + return len; + } + + convert (&sink, arguments, args, formatStr); + return result [0 .. p-result.ptr]; + } + + /********************************************************************** + + Replaces the _format item in a string with the string + equivalent of each argument. + + Params: + formatStr = A string containing _format items. + args = A list of arguments. + + Returns: A copy of formatStr in which the items have been + replaced by the string equivalent of the arguments. + + Remarks: The formatStr parameter is embedded with _format + items of the form: $(BR)$(BR) + {index[,alignment][:_format-string]}$(BR)$(BR) + $(UL $(LI index $(BR) + An integer indicating the element in a list to _format.) + $(LI alignment $(BR) + An optional integer indicating the minimum width. The + result is padded with spaces if the length of the value + is less than alignment.) + $(LI _format-string $(BR) + An optional string of formatting codes.) + )$(BR) + + The leading and trailing braces are required. To include a + literal brace character, use two leading or trailing brace + characters.$(BR)$(BR) + If formatStr is "{0} bottles of beer on the wall" and the + argument is an int with the value of 99, the return value + will be:$(BR) "99 bottles of beer on the wall". + + **********************************************************************/ + + public final T[] convert (T[] formatStr, ...) + { + return convert (_arguments, _argptr, formatStr); + } + + /********************************************************************** + + **********************************************************************/ + + public final uint convert (Sink sink, T[] formatStr, ...) + { + return convert (sink, _arguments, _argptr, formatStr); + } + + /********************************************************************** + + **********************************************************************/ + + public final T[] convert (TypeInfo[] arguments, ArgList args, T[] formatStr) + { + T[] output; + + uint sink (T[] s) + { + output ~= s; + return s.length; + } + + convert (&sink, arguments, args, formatStr); + return output; + } + + /********************************************************************** + + **********************************************************************/ + + public final T[] convertOne (T[] result, TypeInfo ti, Arg arg) + { + return munge (result, null, ti, arg); + } + + /********************************************************************** + + **********************************************************************/ + + public final uint convert (Sink sink, TypeInfo[] arguments, ArgList args, T[] formatStr) + { + assert (formatStr, "null format specifier"); + assert (arguments.length < 64, "too many args in Layout.convert"); + + version (X86_64) + { + Arg[64] arglist = void; + int[64] intargs = void; + byte[64] byteargs = void; + long[64] longargs = void; + short[64] shortargs = void; + void[][64] voidargs = void; + real[64] realargs = void; + float[64] floatargs = void; + double[64] doubleargs = void; + + foreach (i, arg; arguments) + { + arglist[i] = args.ptr; + /* Since floating point types don't live on + * the stack, they must be accessed by the + * correct type. */ + bool converted = false; + switch (arg.classinfo.name[9]) + { + case TypeCode.FLOAT: + floatargs[i] = va_arg!(float)(args); + arglist[i] = &floatargs[i]; + converted = true; + break; + + case TypeCode.DOUBLE: + doubleargs[i] = va_arg!(double)(args); + arglist[i] = &doubleargs[i]; + converted = true; + break; + + case TypeCode.REAL: + realargs[i] = va_arg!(real)(args); + arglist[i] = &realargs[i]; + converted = true; + break; + + default: + break; + } + if (! converted) + { + switch (arg.tsize) + { + case 1: + byteargs[i] = va_arg!(byte)(args); + arglist[i] = &byteargs[i]; + break; + case 2: + shortargs[i] = va_arg!(short)(args); + arglist[i] = &shortargs[i]; + break; + case 4: + intargs[i] = va_arg!(int)(args); + arglist[i] = &intargs[i]; + break; + case 8: + longargs[i] = va_arg!(long)(args); + arglist[i] = &longargs[i]; + break; + case 16: + voidargs[i] = va_arg!(void[])(args); + arglist[i] = &voidargs[i]; + break; + default: + assert (false, "Unknown size: " ~ Integer.toString (arg.tsize)); + } + } + } + } + else + { + Arg[64] arglist = void; + foreach (i, arg; arguments) + { + arglist[i] = args; + args += (arg.tsize + int.sizeof - 1) & ~ (int.sizeof - 1); + } + } + return parse (formatStr, arguments, arglist, sink); + } + + /********************************************************************** + + Parse the format-string, emitting formatted args and text + fragments as we go. + + **********************************************************************/ + + private uint parse (T[] layout, TypeInfo[] ti, Arg[] args, Sink sink) + { + T[384] result = void; + int length, nextIndex; + + + T* s = layout.ptr; + T* fragment = s; + T* end = s + layout.length; + + while (true) + { + while (s < end && *s != '{') + ++s; + + // emit fragment + length += sink (fragment [0 .. s - fragment]); + + // all done? + if (s is end) + break; + + // check for "{{" and skip if so + if (*++s is '{') + { + fragment = s++; + continue; + } + + int index = 0; + bool indexed = false; + + // extract index + while (*s >= '0' && *s <= '9') + { + index = index * 10 + *s++ -'0'; + indexed = true; + } + + // skip spaces + while (s < end && *s is ' ') + ++s; + + int width; + bool leftAlign; + + // has width? + if (*s is ',') + { + while (++s < end && *s is ' ') {} + + if (*s is '-') + { + leftAlign = true; + ++s; + } + + // get width + while (*s >= '0' && *s <= '9') + width = width * 10 + *s++ -'0'; + + // skip spaces + while (s < end && *s is ' ') + ++s; + } + + T[] format; + + // has a format string? + if (*s is ':' && s < end) + { + T* fs = ++s; + + // eat everything up to closing brace + while (s < end && *s != '}') + ++s; + format = fs [0 .. s - fs]; + } + + // insist on a closing brace + if (*s != '}') + { + length += sink ("{missing or misplaced '}'}"); + continue; + } + + // check for default index & set next default counter + if (! indexed) + index = nextIndex; + nextIndex = index + 1; + + // next char is start of following fragment + fragment = ++s; + + // handle alignment + void process (T[] str) + { + int padding = width - str.length; + + // if not left aligned, pad out with spaces + if (! leftAlign && padding > 0) + length += spaces (sink, padding); + + // emit formatted argument + length += sink (str); + + // finally, pad out on right + if (leftAlign && padding > 0) + length += spaces (sink, padding); + } + + // an astonishing number of typehacks needed to handle arrays :( + void processElement (TypeInfo _ti, Arg _arg) + { + if (_ti.classinfo.name.length is 20 && _ti.classinfo.name[9..$] == "StaticArray" ) + { + auto tiStat = cast(TypeInfo_StaticArray)_ti; + auto p = _arg; + length += sink ("[ "); + for (int i = 0; i < tiStat.len; i++) + { + if (p !is _arg ) + length += sink (", "); + processElement (tiStat.value, p); + p += tiStat.tsize; + } + length += sink (" ]"); + } + else + if (_ti.classinfo.name.length is 25 && _ti.classinfo.name[9..$] == "AssociativeArray") + { + auto tiAsso = cast(TypeInfo_AssociativeArray)_ti; + auto tiKey = tiAsso.key; + auto tiVal = tiAsso.next(); + // the knowledge of the internal k/v storage is used + // so this might break if, that internal storage changes + alias ubyte AV; // any type for key, value might be ok, the sizes are corrected later + alias ubyte AK; + auto aa = *cast(AV[AK]*) _arg; + + length += sink ("{ "); + bool first = true; + + int roundUp (int sz) + { + return (sz + (void*).sizeof -1) & ~((void*).sizeof - 1); + } + + foreach (inout v; aa) + { + // the key is befor the value, so substrace with fixed key size from above + auto pk = cast(Arg)( &v - roundUp(AK.sizeof)); + // now the real value pos is plus the real key size + auto pv = cast(Arg)(pk + roundUp(tiKey.tsize())); + + if (!first) + length += sink (", "); + processElement (tiKey, pk); + length += sink ("=>"); + processElement (tiVal, pv); + first = false; + } + length += sink (" }"); + } + else + if (_ti.classinfo.name[9] is TypeCode.ARRAY && + (_ti !is typeid(char[])) && + (_ti !is typeid(wchar[])) && + (_ti !is typeid(dchar[]))) + { + // for all non string array types (including char[][]) + auto arr = *cast(void[]*)_arg; + auto len = arr.length; + auto ptr = cast(Arg) arr.ptr; + auto elTi = _ti.next(); + auto size = elTi.tsize(); + length += sink ("[ "); + while (len > 0) + { + if (ptr !is arr.ptr) + length += sink (", "); + processElement (elTi, ptr); + len -= 1; + ptr += size; + } + length += sink (" ]"); + } + else + // the standard processing + process (munge(result, format, _ti, _arg)); + } + + + // process this argument + if (index >= ti.length) + process ("{invalid index}"); + else + processElement (ti[index], args[index]); + } + return length; + } + + /********************************************************************** + + **********************************************************************/ + + private void error (char[] msg) + { + throw new IllegalArgumentException (msg); + } + + /********************************************************************** + + **********************************************************************/ + + private uint spaces (Sink sink, int count) + { + uint ret; + + static const T[32] Spaces = ' '; + while (count > Spaces.length) + { + ret += sink (Spaces); + count -= Spaces.length; + } + return ret + sink (Spaces[0..count]); + } + + /*********************************************************************** + + ***********************************************************************/ + + private T[] munge (T[] result, T[] format, TypeInfo type, Arg p) + { + switch (type.classinfo.name[9]) + { + case TypeCode.ARRAY: + if (type is typeid(char[])) + return fromUtf8 (*cast(char[]*) p, result); + + if (type is typeid(wchar[])) + return fromUtf16 (*cast(wchar[]*) p, result); + + if (type is typeid(dchar[])) + return fromUtf32 (*cast(dchar[]*) p, result); + + return fromUtf8 (type.toString, result); + + case TypeCode.BOOL: + static T[] t = "true"; + static T[] f = "false"; + return (*cast(bool*) p) ? t : f; + + case TypeCode.BYTE: + return integer (result, *cast(byte*) p, format); + + case TypeCode.UBYTE: + return integer (result, *cast(ubyte*) p, format, 'u'); + + case TypeCode.SHORT: + return integer (result, *cast(short*) p, format); + + case TypeCode.USHORT: + return integer (result, *cast(ushort*) p, format, 'u'); + + case TypeCode.INT: + return integer (result, *cast(int*) p, format); + + case TypeCode.UINT: + return integer (result, *cast(uint*) p, format, 'u'); + + case TypeCode.ULONG: + return integer (result, *cast(long*) p, format, 'u'); + + case TypeCode.LONG: + return integer (result, *cast(long*) p, format); + + case TypeCode.FLOAT: + return floater (result, *cast(float*) p, format); + + case TypeCode.DOUBLE: + return floater (result, *cast(double*) p, format); + + case TypeCode.REAL: + return floater (result, *cast(real*) p, format); + + case TypeCode.CHAR: + return fromUtf8 ((cast(char*) p)[0..1], result); + + case TypeCode.WCHAR: + return fromUtf16 ((cast(wchar*) p)[0..1], result); + + case TypeCode.DCHAR: + return fromUtf32 ((cast(dchar*) p)[0..1], result); + + case TypeCode.POINTER: + return integer (result, *cast(size_t*) p, format, 'x'); + + case TypeCode.CLASS: + auto c = *cast(Object*) p; + if (c) + return fromUtf8 (c.toString, result); + break; + + case TypeCode.STRUCT: + auto s = cast(TypeInfo_Struct) type; + if (s.xtoString) + return fromUtf8 (s.xtoString(p), result); + goto default; + + case TypeCode.INTERFACE: + auto x = *cast(void**) p; + if (x) + { + auto pi = **cast(Interface ***) x; + auto o = cast(Object)(*cast(void**)p - pi.offset); + return fromUtf8 (o.toString, result); + } + break; + + case TypeCode.ENUM: + return munge (result, format, (cast(TypeInfo_Enum) type).base, p); + + case TypeCode.TYPEDEF: + return munge (result, format, (cast(TypeInfo_Typedef) type).base, p); + + default: + return unknown (result, format, type, p); + } + + return cast(T[]) "{null}"; + } + + /********************************************************************** + + **********************************************************************/ + + protected T[] unknown (T[] result, T[] format, TypeInfo type, Arg p) + { + return "{unhandled argument type: " ~ fromUtf8 (type.toString, result) ~ "}"; + } + + /********************************************************************** + + **********************************************************************/ + + protected T[] integer (T[] output, long v, T[] format, T style = 'd') + { + Integer.Flags flags; + uint width = output.length; + + if (parseGeneric (format, width, style)) + if (width <= output.length) + { + output = output [0 .. width]; + flags |= flags.Zero; + } + return Integer.format (output, v, cast(Integer.Style) style, flags); + } + + /********************************************************************** + + **********************************************************************/ + + protected T[] floater (T[] output, real v, T[] format) + { + T style = 'f'; + uint places = 2; + + parseGeneric (format, places, style); + return Float.format (output, v, places, (style is 'e' || style is 'E') ? 0 : 10); + } + + /********************************************************************** + + **********************************************************************/ + + private bool parseGeneric (T[] format, ref uint width, ref T style) + { + if (format.length) + { + uint number; + auto p = format.ptr; + auto e = p + format.length; + style = *p; + while (++p < e) + if (*p >= '0' && *p <= '9') + number = number * 10 + *p - '0'; + else + break; + + if (p - format.ptr > 1) + { + width = number; + return true; + } + } + return false; + } + + /*********************************************************************** + + ***********************************************************************/ + + private static T[] fromUtf8 (char[] s, T[] scratch) + { + static if (is (T == char)) + return s; + + static if (is (T == wchar)) + return Unicode.toString16 (s, scratch); + + static if (is (T == dchar)) + return Unicode.toString32 (s, scratch); + } + + /*********************************************************************** + + ***********************************************************************/ + + private static T[] fromUtf16 (wchar[] s, T[] scratch) + { + static if (is (T == wchar)) + return s; + + static if (is (T == char)) + return Unicode.toString (s, scratch); + + static if (is (T == dchar)) + return Unicode.toString32 (s, scratch); + } + + /*********************************************************************** + + ***********************************************************************/ + + private static T[] fromUtf32 (dchar[] s, T[] scratch) + { + static if (is (T == dchar)) + return s; + + static if (is (T == char)) + return Unicode.toString (s, scratch); + + static if (is (T == wchar)) + return Unicode.toString16 (s, scratch); + } +} + + +/******************************************************************************* + +*******************************************************************************/ + +private enum TypeCode +{ + EMPTY = 0, + BOOL = 'b', + UBYTE = 'h', + BYTE = 'g', + USHORT = 't', + SHORT = 's', + UINT = 'k', + INT = 'i', + ULONG = 'm', + LONG = 'l', + REAL = 'e', + FLOAT = 'f', + DOUBLE = 'd', + CHAR = 'a', + WCHAR = 'u', + DCHAR = 'w', + ARRAY = 'A', + CLASS = 'C', + STRUCT = 'S', + ENUM = 'E', + POINTER = 'P', + TYPEDEF = 'T', + INTERFACE = 'I', +} + + + +/******************************************************************************* + +*******************************************************************************/ + +debug (UnitTest) +{ + //void main() {} + + unittest + { + auto Formatter = new Layout!(char); + + assert( Formatter( "abc" ) == "abc" ); + assert( Formatter( "{0}", 1 ) == "1" ); + assert( Formatter( "{0}", -1 ) == "-1" ); + + assert( Formatter( "{}", 1 ) == "1" ); + assert( Formatter( "{} {}", 1, 2) == "1 2" ); + assert( Formatter( "{} {0} {}", 1, 3) == "1 1 3" ); + assert( Formatter( "{} {0} {} {}", 1, 3) == "1 1 3 {invalid index}" ); + assert( Formatter( "{} {0} {} {:x}", 1, 3) == "1 1 3 {invalid index}" ); + + assert( Formatter( "{0}", true ) == "true" , Formatter( "{0}", true )); + assert( Formatter( "{0}", false ) == "false" ); + + assert( Formatter( "{0}", cast(byte)-128 ) == "-128" ); + assert( Formatter( "{0}", cast(byte)127 ) == "127" ); + assert( Formatter( "{0}", cast(ubyte)255 ) == "255" ); + + assert( Formatter( "{0}", cast(short)-32768 ) == "-32768" ); + assert( Formatter( "{0}", cast(short)32767 ) == "32767" ); + assert( Formatter( "{0}", cast(ushort)65535 ) == "65535" ); + // assert( Formatter( "{0:x4}", cast(ushort)0xafe ) == "0afe" ); + // assert( Formatter( "{0:X4}", cast(ushort)0xafe ) == "0AFE" ); + + assert( Formatter( "{0}", -2147483648 ) == "-2147483648" ); + assert( Formatter( "{0}", 2147483647 ) == "2147483647" ); + assert( Formatter( "{0}", 4294967295 ) == "4294967295" ); + // compiler error + assert( Formatter( "{0}", -9223372036854775807L) == "-9223372036854775807" ); + assert( Formatter( "{0}", 0x8000_0000_0000_0000L) == "9223372036854775808" ); + assert( Formatter( "{0}", 9223372036854775807L ) == "9223372036854775807" ); + // Error: prints -1 + // assert( Formatter( "{0}", 18446744073709551615UL ) == "18446744073709551615" ); + + assert( Formatter( "{0}", "s" ) == "s" ); + // fragments before and after + assert( Formatter( "d{0}d", "s" ) == "dsd" ); + assert( Formatter( "d{0}d", "1234567890" ) == "d1234567890d" ); + + // brace escaping + assert( Formatter( "d{0}d", "<string>" ) == "d<string>d"); + assert( Formatter( "d{{0}d", "<string>" ) == "d{0}d"); + assert( Formatter( "d{{{0}d", "<string>" ) == "d{<string>d"); + assert( Formatter( "d{0}}d", "<string>" ) == "d<string>}d"); + + assert( Formatter( "{0:x}", 0xafe0000 ) == "afe0000" ); + // todo: is it correct to print 7 instead of 6 chars??? + assert( Formatter( "{0:x7}", 0xafe0000 ) == "afe0000" ); + assert( Formatter( "{0:x8}", 0xafe0000 ) == "0afe0000" ); + assert( Formatter( "{0:X8}", 0xafe0000 ) == "0AFE0000" ); + assert( Formatter( "{0:X9}", 0xafe0000 ) == "00AFE0000" ); + assert( Formatter( "{0:X13}", 0xafe0000 ) == "000000AFE0000" ); + assert( Formatter( "{0:x13}", 0xafe0000 ) == "000000afe0000" ); + // decimal width + assert( Formatter( "{0:d6}", 123 ) == "000123" ); + assert( Formatter( "{0,7:d6}", 123 ) == " 000123" ); + assert( Formatter( "{0,-7:d6}", 123 ) == "000123 " ); + + assert( Formatter( "{0:d7}", -123 ) == "-000123" ); + assert( Formatter( "{0,7:d6}", 123 ) == " 000123" ); + assert( Formatter( "{0,7:d7}", -123 ) == "-000123" ); + assert( Formatter( "{0,8:d7}", -123 ) == " -000123" ); + assert( Formatter( "{0,5:d7}", -123 ) == "-000123" ); + + assert( Formatter( "{0:X}", 0xFFFF_FFFF_FFFF_FFFF) == "FFFFFFFFFFFFFFFF" ); + assert( Formatter( "{0:x}", 0xFFFF_FFFF_FFFF_FFFF) == "ffffffffffffffff" ); + assert( Formatter( "{0:x}", 0xFFFF_1234_FFFF_FFFF) == "ffff1234ffffffff" ); + assert( Formatter( "{0:x19}", 0x1234_FFFF_FFFF) == "00000001234ffffffff" ); + // Error: prints -1 + // assert( Formatter( "{0}", 18446744073709551615UL ) == "18446744073709551615" ); + assert( Formatter( "{0}", "s" ) == "s" ); + // fragments before and after + assert( Formatter( "d{0}d", "s" ) == "dsd" ); + + // argument index + assert( Formatter( "a{0}b{1}c{2}", "x", "y", "z" ) == "axbycz" ); + assert( Formatter( "a{2}b{1}c{0}", "x", "y", "z" ) == "azbycx" ); + assert( Formatter( "a{1}b{1}c{1}", "x", "y", "z" ) == "aybycy" ); + + // alignment + // align does not restrict the length + assert( Formatter( "{0,5}", "hellohello" ) == "hellohello" ); + // align fills with spaces + assert( Formatter( "->{0,-10}<-", "hello" ) == "->hello <-" ); + assert( Formatter( "->{0,10}<-", "hello" ) == "-> hello<-" ); + assert( Formatter( "->{0,-10}<-", 12345 ) == "->12345 <-" ); + assert( Formatter( "->{0,10}<-", 12345 ) == "-> 12345<-" ); + + assert( Formatter( "{0:f}", 1.23f ) == "1.23" ); + assert( Formatter( "{0:f4}", 1.23456789L ) == "1.2346" ); + assert( Formatter( "{0:e4}", 0.0001) == "0.1000e-03"); + + int[] a = [ 51, 52, 53, 54, 55 ]; + assert( Formatter( "{}", a ) == "[ 51, 52, 53, 54, 55 ]" ); + assert( Formatter( "{:x}", a ) == "[ 33, 34, 35, 36, 37 ]" ); + assert( Formatter( "{,-4}", a ) == "[ 51 , 52 , 53 , 54 , 55 ]" ); + assert( Formatter( "{,4}", a ) == "[ 51, 52, 53, 54, 55 ]" ); + int[][] b = [ [ 51, 52 ], [ 53, 54, 55 ] ]; + assert( Formatter( "{}", b ) == "[ [ 51, 52 ], [ 53, 54, 55 ] ]" ); + + ushort[3] c = [ cast(ushort)51, 52, 53 ]; + assert( Formatter( "{}", c ) == "[ 51, 52, 53 ]" ); + + ushort[long] d; + d[234] = 2; + d[345] = 3; + assert( Formatter( "{}", d ) == "{ 234=>2, 345=>3 }" ); + + bool[char[]] e; + e[ "key".dup ] = true; + e[ "value".dup ] = false; + assert( Formatter( "{}", e ) == "{ key=>true, value=>false }" ); + + char[][ double ] f; + f[ 1.0 ] = "one".dup; + f[ 3.14 ] = "PI".dup; + assert( Formatter( "{}", f ) == "{ 1.00=>one, 3.14=>PI }" ); + } +} + + + +debug (Layout) +{ + import tango.io.Console; + + void main () + { + auto layout = new Layout!(char); + + Cout (layout ("{:d2}", 56)).newline; + Cout (layout ("{:f4}", 0.001)).newline; + Cout (layout ("{:f8}", 3.14159)).newline; + Cout (layout ("{:e20}", 0.001)).newline; + Cout (layout ("{:e4}", 0.0000001)).newline; + Cout (layout ("ptr:{}", &layout)).newline; + + struct S + { + char[] toString () {return "foo";} + } + + S s; + Cout (layout ("struct: {}", s)).newline; + } +}