132
|
1 /*******************************************************************************
|
|
2
|
|
3 copyright: Copyright (c) 2005 Kris. All rights reserved
|
|
4
|
|
5 license: BSD style: $(LICENSE)
|
|
6
|
|
7 version: Initial release: 2005
|
|
8
|
|
9 author: Kris
|
|
10
|
|
11 This module provides a general-purpose formatting system to
|
|
12 convert values to text suitable for display. There is support
|
|
13 for alignment, justification, and common format specifiers for
|
|
14 numbers.
|
|
15
|
|
16 Layout can be customized via configuring various handlers and
|
|
17 associated meta-date. This is utilized to plug in text.locale
|
|
18 for handling custom formats, date/time and culture-specific
|
|
19 conversions.
|
|
20
|
|
21 The format notation is influenced by that used by the .NET
|
|
22 and ICU frameworks, rather than C-style printf or D-style
|
|
23 writef notation.
|
|
24
|
|
25 ******************************************************************************/
|
|
26
|
|
27 module tango.text.convert.Layout;
|
|
28
|
|
29 private import tango.core.Exception;
|
|
30
|
|
31 private import Unicode = tango.text.convert.Utf;
|
|
32
|
|
33 private import Float = tango.text.convert.Float,
|
|
34 Integer = tango.text.convert.Integer;
|
|
35
|
|
36 /*******************************************************************************
|
|
37
|
|
38 Platform issues ...
|
|
39
|
|
40 *******************************************************************************/
|
|
41
|
|
42 version (DigitalMars)
|
|
43 {
|
|
44 alias void* Arg;
|
|
45 alias void* ArgList;
|
|
46 }
|
|
47 else
|
|
48 version (X86_64)
|
|
49 {
|
|
50 private import std.stdarg;
|
|
51 alias void* Arg;
|
|
52 alias va_list ArgList;
|
|
53 }
|
|
54 else
|
|
55 {
|
|
56 alias char* Arg;
|
|
57 alias char* ArgList;
|
|
58 }
|
|
59
|
|
60 /*******************************************************************************
|
|
61
|
|
62 Contains methods for replacing format items in a string with string
|
|
63 equivalents of each argument.
|
|
64
|
|
65 *******************************************************************************/
|
|
66
|
|
67 class Layout(T)
|
|
68 {
|
|
69 public alias convert opCall;
|
|
70 public alias uint delegate (T[]) Sink;
|
|
71
|
|
72 /**********************************************************************
|
|
73
|
|
74 **********************************************************************/
|
|
75
|
|
76 public final T[] sprint (T[] result, T[] formatStr, ...)
|
|
77 {
|
|
78 return vprint (result, formatStr, _arguments, _argptr);
|
|
79 }
|
|
80
|
|
81 /**********************************************************************
|
|
82
|
|
83 **********************************************************************/
|
|
84
|
|
85 public final T[] vprint (T[] result, T[] formatStr, TypeInfo[] arguments, ArgList args)
|
|
86 {
|
|
87 T* p = result.ptr;
|
|
88
|
|
89 uint sink (T[] s)
|
|
90 {
|
|
91 int len = s.length;
|
|
92 if (len < (result.ptr + result.length) - p)
|
|
93 {
|
|
94 p [0..len] = s;
|
|
95 p += len;
|
|
96 }
|
|
97 else
|
|
98 error ("Layout.vprint :: output buffer is full");
|
|
99 return len;
|
|
100 }
|
|
101
|
|
102 convert (&sink, arguments, args, formatStr);
|
|
103 return result [0 .. p-result.ptr];
|
|
104 }
|
|
105
|
|
106 /**********************************************************************
|
|
107
|
|
108 Replaces the _format item in a string with the string
|
|
109 equivalent of each argument.
|
|
110
|
|
111 Params:
|
|
112 formatStr = A string containing _format items.
|
|
113 args = A list of arguments.
|
|
114
|
|
115 Returns: A copy of formatStr in which the items have been
|
|
116 replaced by the string equivalent of the arguments.
|
|
117
|
|
118 Remarks: The formatStr parameter is embedded with _format
|
|
119 items of the form: $(BR)$(BR)
|
|
120 {index[,alignment][:_format-string]}$(BR)$(BR)
|
|
121 $(UL $(LI index $(BR)
|
|
122 An integer indicating the element in a list to _format.)
|
|
123 $(LI alignment $(BR)
|
|
124 An optional integer indicating the minimum width. The
|
|
125 result is padded with spaces if the length of the value
|
|
126 is less than alignment.)
|
|
127 $(LI _format-string $(BR)
|
|
128 An optional string of formatting codes.)
|
|
129 )$(BR)
|
|
130
|
|
131 The leading and trailing braces are required. To include a
|
|
132 literal brace character, use two leading or trailing brace
|
|
133 characters.$(BR)$(BR)
|
|
134 If formatStr is "{0} bottles of beer on the wall" and the
|
|
135 argument is an int with the value of 99, the return value
|
|
136 will be:$(BR) "99 bottles of beer on the wall".
|
|
137
|
|
138 **********************************************************************/
|
|
139
|
|
140 public final T[] convert (T[] formatStr, ...)
|
|
141 {
|
|
142 return convert (_arguments, _argptr, formatStr);
|
|
143 }
|
|
144
|
|
145 /**********************************************************************
|
|
146
|
|
147 **********************************************************************/
|
|
148
|
|
149 public final uint convert (Sink sink, T[] formatStr, ...)
|
|
150 {
|
|
151 return convert (sink, _arguments, _argptr, formatStr);
|
|
152 }
|
|
153
|
|
154 /**********************************************************************
|
|
155
|
|
156 **********************************************************************/
|
|
157
|
|
158 public final T[] convert (TypeInfo[] arguments, ArgList args, T[] formatStr)
|
|
159 {
|
|
160 T[] output;
|
|
161
|
|
162 uint sink (T[] s)
|
|
163 {
|
|
164 output ~= s;
|
|
165 return s.length;
|
|
166 }
|
|
167
|
|
168 convert (&sink, arguments, args, formatStr);
|
|
169 return output;
|
|
170 }
|
|
171
|
|
172 /**********************************************************************
|
|
173
|
|
174 **********************************************************************/
|
|
175
|
|
176 public final T[] convertOne (T[] result, TypeInfo ti, Arg arg)
|
|
177 {
|
|
178 return munge (result, null, ti, arg);
|
|
179 }
|
|
180
|
|
181 /**********************************************************************
|
|
182
|
|
183 **********************************************************************/
|
|
184
|
|
185 public final uint convert (Sink sink, TypeInfo[] arguments, ArgList args, T[] formatStr)
|
|
186 {
|
|
187 assert (formatStr, "null format specifier");
|
|
188 assert (arguments.length < 64, "too many args in Layout.convert");
|
|
189
|
|
190 version (X86_64)
|
|
191 {
|
|
192 Arg[64] arglist = void;
|
|
193 int[64] intargs = void;
|
|
194 byte[64] byteargs = void;
|
|
195 long[64] longargs = void;
|
|
196 short[64] shortargs = void;
|
|
197 void[][64] voidargs = void;
|
|
198 real[64] realargs = void;
|
|
199 float[64] floatargs = void;
|
|
200 double[64] doubleargs = void;
|
|
201
|
|
202 foreach (i, arg; arguments)
|
|
203 {
|
|
204 arglist[i] = args.ptr;
|
|
205 /* Since floating point types don't live on
|
|
206 * the stack, they must be accessed by the
|
|
207 * correct type. */
|
|
208 bool converted = false;
|
|
209 switch (arg.classinfo.name[9])
|
|
210 {
|
|
211 case TypeCode.FLOAT:
|
|
212 floatargs[i] = va_arg!(float)(args);
|
|
213 arglist[i] = &floatargs[i];
|
|
214 converted = true;
|
|
215 break;
|
|
216
|
|
217 case TypeCode.DOUBLE:
|
|
218 doubleargs[i] = va_arg!(double)(args);
|
|
219 arglist[i] = &doubleargs[i];
|
|
220 converted = true;
|
|
221 break;
|
|
222
|
|
223 case TypeCode.REAL:
|
|
224 realargs[i] = va_arg!(real)(args);
|
|
225 arglist[i] = &realargs[i];
|
|
226 converted = true;
|
|
227 break;
|
|
228
|
|
229 default:
|
|
230 break;
|
|
231 }
|
|
232 if (! converted)
|
|
233 {
|
|
234 switch (arg.tsize)
|
|
235 {
|
|
236 case 1:
|
|
237 byteargs[i] = va_arg!(byte)(args);
|
|
238 arglist[i] = &byteargs[i];
|
|
239 break;
|
|
240 case 2:
|
|
241 shortargs[i] = va_arg!(short)(args);
|
|
242 arglist[i] = &shortargs[i];
|
|
243 break;
|
|
244 case 4:
|
|
245 intargs[i] = va_arg!(int)(args);
|
|
246 arglist[i] = &intargs[i];
|
|
247 break;
|
|
248 case 8:
|
|
249 longargs[i] = va_arg!(long)(args);
|
|
250 arglist[i] = &longargs[i];
|
|
251 break;
|
|
252 case 16:
|
|
253 voidargs[i] = va_arg!(void[])(args);
|
|
254 arglist[i] = &voidargs[i];
|
|
255 break;
|
|
256 default:
|
|
257 assert (false, "Unknown size: " ~ Integer.toString (arg.tsize));
|
|
258 }
|
|
259 }
|
|
260 }
|
|
261 }
|
|
262 else
|
|
263 {
|
|
264 Arg[64] arglist = void;
|
|
265 foreach (i, arg; arguments)
|
|
266 {
|
|
267 arglist[i] = args;
|
|
268 args += (arg.tsize + int.sizeof - 1) & ~ (int.sizeof - 1);
|
|
269 }
|
|
270 }
|
|
271 return parse (formatStr, arguments, arglist, sink);
|
|
272 }
|
|
273
|
|
274 /**********************************************************************
|
|
275
|
|
276 Parse the format-string, emitting formatted args and text
|
|
277 fragments as we go.
|
|
278
|
|
279 **********************************************************************/
|
|
280
|
|
281 private uint parse (T[] layout, TypeInfo[] ti, Arg[] args, Sink sink)
|
|
282 {
|
|
283 T[384] result = void;
|
|
284 int length, nextIndex;
|
|
285
|
|
286
|
|
287 T* s = layout.ptr;
|
|
288 T* fragment = s;
|
|
289 T* end = s + layout.length;
|
|
290
|
|
291 while (true)
|
|
292 {
|
|
293 while (s < end && *s != '{')
|
|
294 ++s;
|
|
295
|
|
296 // emit fragment
|
|
297 length += sink (fragment [0 .. s - fragment]);
|
|
298
|
|
299 // all done?
|
|
300 if (s is end)
|
|
301 break;
|
|
302
|
|
303 // check for "{{" and skip if so
|
|
304 if (*++s is '{')
|
|
305 {
|
|
306 fragment = s++;
|
|
307 continue;
|
|
308 }
|
|
309
|
|
310 int index = 0;
|
|
311 bool indexed = false;
|
|
312
|
|
313 // extract index
|
|
314 while (*s >= '0' && *s <= '9')
|
|
315 {
|
|
316 index = index * 10 + *s++ -'0';
|
|
317 indexed = true;
|
|
318 }
|
|
319
|
|
320 // skip spaces
|
|
321 while (s < end && *s is ' ')
|
|
322 ++s;
|
|
323
|
|
324 int width;
|
|
325 bool leftAlign;
|
|
326
|
|
327 // has width?
|
|
328 if (*s is ',')
|
|
329 {
|
|
330 while (++s < end && *s is ' ') {}
|
|
331
|
|
332 if (*s is '-')
|
|
333 {
|
|
334 leftAlign = true;
|
|
335 ++s;
|
|
336 }
|
|
337
|
|
338 // get width
|
|
339 while (*s >= '0' && *s <= '9')
|
|
340 width = width * 10 + *s++ -'0';
|
|
341
|
|
342 // skip spaces
|
|
343 while (s < end && *s is ' ')
|
|
344 ++s;
|
|
345 }
|
|
346
|
|
347 T[] format;
|
|
348
|
|
349 // has a format string?
|
|
350 if (*s is ':' && s < end)
|
|
351 {
|
|
352 T* fs = ++s;
|
|
353
|
|
354 // eat everything up to closing brace
|
|
355 while (s < end && *s != '}')
|
|
356 ++s;
|
|
357 format = fs [0 .. s - fs];
|
|
358 }
|
|
359
|
|
360 // insist on a closing brace
|
|
361 if (*s != '}')
|
|
362 {
|
|
363 length += sink ("{missing or misplaced '}'}");
|
|
364 continue;
|
|
365 }
|
|
366
|
|
367 // check for default index & set next default counter
|
|
368 if (! indexed)
|
|
369 index = nextIndex;
|
|
370 nextIndex = index + 1;
|
|
371
|
|
372 // next char is start of following fragment
|
|
373 fragment = ++s;
|
|
374
|
|
375 // handle alignment
|
|
376 void process (T[] str)
|
|
377 {
|
|
378 int padding = width - str.length;
|
|
379
|
|
380 // if not left aligned, pad out with spaces
|
|
381 if (! leftAlign && padding > 0)
|
|
382 length += spaces (sink, padding);
|
|
383
|
|
384 // emit formatted argument
|
|
385 length += sink (str);
|
|
386
|
|
387 // finally, pad out on right
|
|
388 if (leftAlign && padding > 0)
|
|
389 length += spaces (sink, padding);
|
|
390 }
|
|
391
|
|
392 // an astonishing number of typehacks needed to handle arrays :(
|
|
393 void processElement (TypeInfo _ti, Arg _arg)
|
|
394 {
|
|
395 if (_ti.classinfo.name.length is 20 && _ti.classinfo.name[9..$] == "StaticArray" )
|
|
396 {
|
|
397 auto tiStat = cast(TypeInfo_StaticArray)_ti;
|
|
398 auto p = _arg;
|
|
399 length += sink ("[ ");
|
|
400 for (int i = 0; i < tiStat.len; i++)
|
|
401 {
|
|
402 if (p !is _arg )
|
|
403 length += sink (", ");
|
|
404 processElement (tiStat.value, p);
|
|
405 p += tiStat.tsize;
|
|
406 }
|
|
407 length += sink (" ]");
|
|
408 }
|
|
409 else
|
|
410 if (_ti.classinfo.name.length is 25 && _ti.classinfo.name[9..$] == "AssociativeArray")
|
|
411 {
|
|
412 auto tiAsso = cast(TypeInfo_AssociativeArray)_ti;
|
|
413 auto tiKey = tiAsso.key;
|
|
414 auto tiVal = tiAsso.next();
|
|
415 // the knowledge of the internal k/v storage is used
|
|
416 // so this might break if, that internal storage changes
|
|
417 alias ubyte AV; // any type for key, value might be ok, the sizes are corrected later
|
|
418 alias ubyte AK;
|
|
419 auto aa = *cast(AV[AK]*) _arg;
|
|
420
|
|
421 length += sink ("{ ");
|
|
422 bool first = true;
|
|
423
|
|
424 int roundUp (int sz)
|
|
425 {
|
|
426 return (sz + (void*).sizeof -1) & ~((void*).sizeof - 1);
|
|
427 }
|
|
428
|
|
429 foreach (inout v; aa)
|
|
430 {
|
|
431 // the key is befor the value, so substrace with fixed key size from above
|
|
432 auto pk = cast(Arg)( &v - roundUp(AK.sizeof));
|
|
433 // now the real value pos is plus the real key size
|
|
434 auto pv = cast(Arg)(pk + roundUp(tiKey.tsize()));
|
|
435
|
|
436 if (!first)
|
|
437 length += sink (", ");
|
|
438 processElement (tiKey, pk);
|
|
439 length += sink ("=>");
|
|
440 processElement (tiVal, pv);
|
|
441 first = false;
|
|
442 }
|
|
443 length += sink (" }");
|
|
444 }
|
|
445 else
|
|
446 if (_ti.classinfo.name[9] is TypeCode.ARRAY &&
|
|
447 (_ti !is typeid(char[])) &&
|
|
448 (_ti !is typeid(wchar[])) &&
|
|
449 (_ti !is typeid(dchar[])))
|
|
450 {
|
|
451 // for all non string array types (including char[][])
|
|
452 auto arr = *cast(void[]*)_arg;
|
|
453 auto len = arr.length;
|
|
454 auto ptr = cast(Arg) arr.ptr;
|
|
455 auto elTi = _ti.next();
|
|
456 auto size = elTi.tsize();
|
|
457 length += sink ("[ ");
|
|
458 while (len > 0)
|
|
459 {
|
|
460 if (ptr !is arr.ptr)
|
|
461 length += sink (", ");
|
|
462 processElement (elTi, ptr);
|
|
463 len -= 1;
|
|
464 ptr += size;
|
|
465 }
|
|
466 length += sink (" ]");
|
|
467 }
|
|
468 else
|
|
469 // the standard processing
|
|
470 process (munge(result, format, _ti, _arg));
|
|
471 }
|
|
472
|
|
473
|
|
474 // process this argument
|
|
475 if (index >= ti.length)
|
|
476 process ("{invalid index}");
|
|
477 else
|
|
478 processElement (ti[index], args[index]);
|
|
479 }
|
|
480 return length;
|
|
481 }
|
|
482
|
|
483 /**********************************************************************
|
|
484
|
|
485 **********************************************************************/
|
|
486
|
|
487 private void error (char[] msg)
|
|
488 {
|
|
489 throw new IllegalArgumentException (msg);
|
|
490 }
|
|
491
|
|
492 /**********************************************************************
|
|
493
|
|
494 **********************************************************************/
|
|
495
|
|
496 private uint spaces (Sink sink, int count)
|
|
497 {
|
|
498 uint ret;
|
|
499
|
|
500 static const T[32] Spaces = ' ';
|
|
501 while (count > Spaces.length)
|
|
502 {
|
|
503 ret += sink (Spaces);
|
|
504 count -= Spaces.length;
|
|
505 }
|
|
506 return ret + sink (Spaces[0..count]);
|
|
507 }
|
|
508
|
|
509 /***********************************************************************
|
|
510
|
|
511 ***********************************************************************/
|
|
512
|
|
513 private T[] munge (T[] result, T[] format, TypeInfo type, Arg p)
|
|
514 {
|
|
515 switch (type.classinfo.name[9])
|
|
516 {
|
|
517 case TypeCode.ARRAY:
|
|
518 if (type is typeid(char[]))
|
|
519 return fromUtf8 (*cast(char[]*) p, result);
|
|
520
|
|
521 if (type is typeid(wchar[]))
|
|
522 return fromUtf16 (*cast(wchar[]*) p, result);
|
|
523
|
|
524 if (type is typeid(dchar[]))
|
|
525 return fromUtf32 (*cast(dchar[]*) p, result);
|
|
526
|
|
527 return fromUtf8 (type.toString, result);
|
|
528
|
|
529 case TypeCode.BOOL:
|
|
530 static T[] t = "true";
|
|
531 static T[] f = "false";
|
|
532 return (*cast(bool*) p) ? t : f;
|
|
533
|
|
534 case TypeCode.BYTE:
|
|
535 return integer (result, *cast(byte*) p, format);
|
|
536
|
|
537 case TypeCode.UBYTE:
|
|
538 return integer (result, *cast(ubyte*) p, format, 'u');
|
|
539
|
|
540 case TypeCode.SHORT:
|
|
541 return integer (result, *cast(short*) p, format);
|
|
542
|
|
543 case TypeCode.USHORT:
|
|
544 return integer (result, *cast(ushort*) p, format, 'u');
|
|
545
|
|
546 case TypeCode.INT:
|
|
547 return integer (result, *cast(int*) p, format);
|
|
548
|
|
549 case TypeCode.UINT:
|
|
550 return integer (result, *cast(uint*) p, format, 'u');
|
|
551
|
|
552 case TypeCode.ULONG:
|
|
553 return integer (result, *cast(long*) p, format, 'u');
|
|
554
|
|
555 case TypeCode.LONG:
|
|
556 return integer (result, *cast(long*) p, format);
|
|
557
|
|
558 case TypeCode.FLOAT:
|
|
559 return floater (result, *cast(float*) p, format);
|
|
560
|
|
561 case TypeCode.DOUBLE:
|
|
562 return floater (result, *cast(double*) p, format);
|
|
563
|
|
564 case TypeCode.REAL:
|
|
565 return floater (result, *cast(real*) p, format);
|
|
566
|
|
567 case TypeCode.CHAR:
|
|
568 return fromUtf8 ((cast(char*) p)[0..1], result);
|
|
569
|
|
570 case TypeCode.WCHAR:
|
|
571 return fromUtf16 ((cast(wchar*) p)[0..1], result);
|
|
572
|
|
573 case TypeCode.DCHAR:
|
|
574 return fromUtf32 ((cast(dchar*) p)[0..1], result);
|
|
575
|
|
576 case TypeCode.POINTER:
|
|
577 return integer (result, *cast(size_t*) p, format, 'x');
|
|
578
|
|
579 case TypeCode.CLASS:
|
|
580 auto c = *cast(Object*) p;
|
|
581 if (c)
|
|
582 return fromUtf8 (c.toString, result);
|
|
583 break;
|
|
584
|
|
585 case TypeCode.STRUCT:
|
|
586 auto s = cast(TypeInfo_Struct) type;
|
|
587 if (s.xtoString)
|
|
588 return fromUtf8 (s.xtoString(p), result);
|
|
589 goto default;
|
|
590
|
|
591 case TypeCode.INTERFACE:
|
|
592 auto x = *cast(void**) p;
|
|
593 if (x)
|
|
594 {
|
|
595 auto pi = **cast(Interface ***) x;
|
|
596 auto o = cast(Object)(*cast(void**)p - pi.offset);
|
|
597 return fromUtf8 (o.toString, result);
|
|
598 }
|
|
599 break;
|
|
600
|
|
601 case TypeCode.ENUM:
|
|
602 return munge (result, format, (cast(TypeInfo_Enum) type).base, p);
|
|
603
|
|
604 case TypeCode.TYPEDEF:
|
|
605 return munge (result, format, (cast(TypeInfo_Typedef) type).base, p);
|
|
606
|
|
607 default:
|
|
608 return unknown (result, format, type, p);
|
|
609 }
|
|
610
|
|
611 return cast(T[]) "{null}";
|
|
612 }
|
|
613
|
|
614 /**********************************************************************
|
|
615
|
|
616 **********************************************************************/
|
|
617
|
|
618 protected T[] unknown (T[] result, T[] format, TypeInfo type, Arg p)
|
|
619 {
|
|
620 return "{unhandled argument type: " ~ fromUtf8 (type.toString, result) ~ "}";
|
|
621 }
|
|
622
|
|
623 /**********************************************************************
|
|
624
|
|
625 **********************************************************************/
|
|
626
|
|
627 protected T[] integer (T[] output, long v, T[] format, T style = 'd')
|
|
628 {
|
|
629 Integer.Flags flags;
|
|
630 uint width = output.length;
|
|
631
|
|
632 if (parseGeneric (format, width, style))
|
|
633 if (width <= output.length)
|
|
634 {
|
|
635 output = output [0 .. width];
|
|
636 flags |= flags.Zero;
|
|
637 }
|
|
638 return Integer.format (output, v, cast(Integer.Style) style, flags);
|
|
639 }
|
|
640
|
|
641 /**********************************************************************
|
|
642
|
|
643 **********************************************************************/
|
|
644
|
|
645 protected T[] floater (T[] output, real v, T[] format)
|
|
646 {
|
|
647 T style = 'f';
|
|
648 uint places = 2;
|
|
649
|
|
650 parseGeneric (format, places, style);
|
|
651 return Float.format (output, v, places, (style is 'e' || style is 'E') ? 0 : 10);
|
|
652 }
|
|
653
|
|
654 /**********************************************************************
|
|
655
|
|
656 **********************************************************************/
|
|
657
|
|
658 private bool parseGeneric (T[] format, ref uint width, ref T style)
|
|
659 {
|
|
660 if (format.length)
|
|
661 {
|
|
662 uint number;
|
|
663 auto p = format.ptr;
|
|
664 auto e = p + format.length;
|
|
665 style = *p;
|
|
666 while (++p < e)
|
|
667 if (*p >= '0' && *p <= '9')
|
|
668 number = number * 10 + *p - '0';
|
|
669 else
|
|
670 break;
|
|
671
|
|
672 if (p - format.ptr > 1)
|
|
673 {
|
|
674 width = number;
|
|
675 return true;
|
|
676 }
|
|
677 }
|
|
678 return false;
|
|
679 }
|
|
680
|
|
681 /***********************************************************************
|
|
682
|
|
683 ***********************************************************************/
|
|
684
|
|
685 private static T[] fromUtf8 (char[] s, T[] scratch)
|
|
686 {
|
|
687 static if (is (T == char))
|
|
688 return s;
|
|
689
|
|
690 static if (is (T == wchar))
|
|
691 return Unicode.toString16 (s, scratch);
|
|
692
|
|
693 static if (is (T == dchar))
|
|
694 return Unicode.toString32 (s, scratch);
|
|
695 }
|
|
696
|
|
697 /***********************************************************************
|
|
698
|
|
699 ***********************************************************************/
|
|
700
|
|
701 private static T[] fromUtf16 (wchar[] s, T[] scratch)
|
|
702 {
|
|
703 static if (is (T == wchar))
|
|
704 return s;
|
|
705
|
|
706 static if (is (T == char))
|
|
707 return Unicode.toString (s, scratch);
|
|
708
|
|
709 static if (is (T == dchar))
|
|
710 return Unicode.toString32 (s, scratch);
|
|
711 }
|
|
712
|
|
713 /***********************************************************************
|
|
714
|
|
715 ***********************************************************************/
|
|
716
|
|
717 private static T[] fromUtf32 (dchar[] s, T[] scratch)
|
|
718 {
|
|
719 static if (is (T == dchar))
|
|
720 return s;
|
|
721
|
|
722 static if (is (T == char))
|
|
723 return Unicode.toString (s, scratch);
|
|
724
|
|
725 static if (is (T == wchar))
|
|
726 return Unicode.toString16 (s, scratch);
|
|
727 }
|
|
728 }
|
|
729
|
|
730
|
|
731 /*******************************************************************************
|
|
732
|
|
733 *******************************************************************************/
|
|
734
|
|
735 private enum TypeCode
|
|
736 {
|
|
737 EMPTY = 0,
|
|
738 BOOL = 'b',
|
|
739 UBYTE = 'h',
|
|
740 BYTE = 'g',
|
|
741 USHORT = 't',
|
|
742 SHORT = 's',
|
|
743 UINT = 'k',
|
|
744 INT = 'i',
|
|
745 ULONG = 'm',
|
|
746 LONG = 'l',
|
|
747 REAL = 'e',
|
|
748 FLOAT = 'f',
|
|
749 DOUBLE = 'd',
|
|
750 CHAR = 'a',
|
|
751 WCHAR = 'u',
|
|
752 DCHAR = 'w',
|
|
753 ARRAY = 'A',
|
|
754 CLASS = 'C',
|
|
755 STRUCT = 'S',
|
|
756 ENUM = 'E',
|
|
757 POINTER = 'P',
|
|
758 TYPEDEF = 'T',
|
|
759 INTERFACE = 'I',
|
|
760 }
|
|
761
|
|
762
|
|
763
|
|
764 /*******************************************************************************
|
|
765
|
|
766 *******************************************************************************/
|
|
767
|
|
768 debug (UnitTest)
|
|
769 {
|
|
770 //void main() {}
|
|
771
|
|
772 unittest
|
|
773 {
|
|
774 auto Formatter = new Layout!(char);
|
|
775
|
|
776 assert( Formatter( "abc" ) == "abc" );
|
|
777 assert( Formatter( "{0}", 1 ) == "1" );
|
|
778 assert( Formatter( "{0}", -1 ) == "-1" );
|
|
779
|
|
780 assert( Formatter( "{}", 1 ) == "1" );
|
|
781 assert( Formatter( "{} {}", 1, 2) == "1 2" );
|
|
782 assert( Formatter( "{} {0} {}", 1, 3) == "1 1 3" );
|
|
783 assert( Formatter( "{} {0} {} {}", 1, 3) == "1 1 3 {invalid index}" );
|
|
784 assert( Formatter( "{} {0} {} {:x}", 1, 3) == "1 1 3 {invalid index}" );
|
|
785
|
|
786 assert( Formatter( "{0}", true ) == "true" , Formatter( "{0}", true ));
|
|
787 assert( Formatter( "{0}", false ) == "false" );
|
|
788
|
|
789 assert( Formatter( "{0}", cast(byte)-128 ) == "-128" );
|
|
790 assert( Formatter( "{0}", cast(byte)127 ) == "127" );
|
|
791 assert( Formatter( "{0}", cast(ubyte)255 ) == "255" );
|
|
792
|
|
793 assert( Formatter( "{0}", cast(short)-32768 ) == "-32768" );
|
|
794 assert( Formatter( "{0}", cast(short)32767 ) == "32767" );
|
|
795 assert( Formatter( "{0}", cast(ushort)65535 ) == "65535" );
|
|
796 // assert( Formatter( "{0:x4}", cast(ushort)0xafe ) == "0afe" );
|
|
797 // assert( Formatter( "{0:X4}", cast(ushort)0xafe ) == "0AFE" );
|
|
798
|
|
799 assert( Formatter( "{0}", -2147483648 ) == "-2147483648" );
|
|
800 assert( Formatter( "{0}", 2147483647 ) == "2147483647" );
|
|
801 assert( Formatter( "{0}", 4294967295 ) == "4294967295" );
|
|
802 // compiler error
|
|
803 assert( Formatter( "{0}", -9223372036854775807L) == "-9223372036854775807" );
|
|
804 assert( Formatter( "{0}", 0x8000_0000_0000_0000L) == "9223372036854775808" );
|
|
805 assert( Formatter( "{0}", 9223372036854775807L ) == "9223372036854775807" );
|
|
806 // Error: prints -1
|
|
807 // assert( Formatter( "{0}", 18446744073709551615UL ) == "18446744073709551615" );
|
|
808
|
|
809 assert( Formatter( "{0}", "s" ) == "s" );
|
|
810 // fragments before and after
|
|
811 assert( Formatter( "d{0}d", "s" ) == "dsd" );
|
|
812 assert( Formatter( "d{0}d", "1234567890" ) == "d1234567890d" );
|
|
813
|
|
814 // brace escaping
|
|
815 assert( Formatter( "d{0}d", "<string>" ) == "d<string>d");
|
|
816 assert( Formatter( "d{{0}d", "<string>" ) == "d{0}d");
|
|
817 assert( Formatter( "d{{{0}d", "<string>" ) == "d{<string>d");
|
|
818 assert( Formatter( "d{0}}d", "<string>" ) == "d<string>}d");
|
|
819
|
|
820 assert( Formatter( "{0:x}", 0xafe0000 ) == "afe0000" );
|
|
821 // todo: is it correct to print 7 instead of 6 chars???
|
|
822 assert( Formatter( "{0:x7}", 0xafe0000 ) == "afe0000" );
|
|
823 assert( Formatter( "{0:x8}", 0xafe0000 ) == "0afe0000" );
|
|
824 assert( Formatter( "{0:X8}", 0xafe0000 ) == "0AFE0000" );
|
|
825 assert( Formatter( "{0:X9}", 0xafe0000 ) == "00AFE0000" );
|
|
826 assert( Formatter( "{0:X13}", 0xafe0000 ) == "000000AFE0000" );
|
|
827 assert( Formatter( "{0:x13}", 0xafe0000 ) == "000000afe0000" );
|
|
828 // decimal width
|
|
829 assert( Formatter( "{0:d6}", 123 ) == "000123" );
|
|
830 assert( Formatter( "{0,7:d6}", 123 ) == " 000123" );
|
|
831 assert( Formatter( "{0,-7:d6}", 123 ) == "000123 " );
|
|
832
|
|
833 assert( Formatter( "{0:d7}", -123 ) == "-000123" );
|
|
834 assert( Formatter( "{0,7:d6}", 123 ) == " 000123" );
|
|
835 assert( Formatter( "{0,7:d7}", -123 ) == "-000123" );
|
|
836 assert( Formatter( "{0,8:d7}", -123 ) == " -000123" );
|
|
837 assert( Formatter( "{0,5:d7}", -123 ) == "-000123" );
|
|
838
|
|
839 assert( Formatter( "{0:X}", 0xFFFF_FFFF_FFFF_FFFF) == "FFFFFFFFFFFFFFFF" );
|
|
840 assert( Formatter( "{0:x}", 0xFFFF_FFFF_FFFF_FFFF) == "ffffffffffffffff" );
|
|
841 assert( Formatter( "{0:x}", 0xFFFF_1234_FFFF_FFFF) == "ffff1234ffffffff" );
|
|
842 assert( Formatter( "{0:x19}", 0x1234_FFFF_FFFF) == "00000001234ffffffff" );
|
|
843 // Error: prints -1
|
|
844 // assert( Formatter( "{0}", 18446744073709551615UL ) == "18446744073709551615" );
|
|
845 assert( Formatter( "{0}", "s" ) == "s" );
|
|
846 // fragments before and after
|
|
847 assert( Formatter( "d{0}d", "s" ) == "dsd" );
|
|
848
|
|
849 // argument index
|
|
850 assert( Formatter( "a{0}b{1}c{2}", "x", "y", "z" ) == "axbycz" );
|
|
851 assert( Formatter( "a{2}b{1}c{0}", "x", "y", "z" ) == "azbycx" );
|
|
852 assert( Formatter( "a{1}b{1}c{1}", "x", "y", "z" ) == "aybycy" );
|
|
853
|
|
854 // alignment
|
|
855 // align does not restrict the length
|
|
856 assert( Formatter( "{0,5}", "hellohello" ) == "hellohello" );
|
|
857 // align fills with spaces
|
|
858 assert( Formatter( "->{0,-10}<-", "hello" ) == "->hello <-" );
|
|
859 assert( Formatter( "->{0,10}<-", "hello" ) == "-> hello<-" );
|
|
860 assert( Formatter( "->{0,-10}<-", 12345 ) == "->12345 <-" );
|
|
861 assert( Formatter( "->{0,10}<-", 12345 ) == "-> 12345<-" );
|
|
862
|
|
863 assert( Formatter( "{0:f}", 1.23f ) == "1.23" );
|
|
864 assert( Formatter( "{0:f4}", 1.23456789L ) == "1.2346" );
|
|
865 assert( Formatter( "{0:e4}", 0.0001) == "0.1000e-03");
|
|
866
|
|
867 int[] a = [ 51, 52, 53, 54, 55 ];
|
|
868 assert( Formatter( "{}", a ) == "[ 51, 52, 53, 54, 55 ]" );
|
|
869 assert( Formatter( "{:x}", a ) == "[ 33, 34, 35, 36, 37 ]" );
|
|
870 assert( Formatter( "{,-4}", a ) == "[ 51 , 52 , 53 , 54 , 55 ]" );
|
|
871 assert( Formatter( "{,4}", a ) == "[ 51, 52, 53, 54, 55 ]" );
|
|
872 int[][] b = [ [ 51, 52 ], [ 53, 54, 55 ] ];
|
|
873 assert( Formatter( "{}", b ) == "[ [ 51, 52 ], [ 53, 54, 55 ] ]" );
|
|
874
|
|
875 ushort[3] c = [ cast(ushort)51, 52, 53 ];
|
|
876 assert( Formatter( "{}", c ) == "[ 51, 52, 53 ]" );
|
|
877
|
|
878 ushort[long] d;
|
|
879 d[234] = 2;
|
|
880 d[345] = 3;
|
|
881 assert( Formatter( "{}", d ) == "{ 234=>2, 345=>3 }" );
|
|
882
|
|
883 bool[char[]] e;
|
|
884 e[ "key".dup ] = true;
|
|
885 e[ "value".dup ] = false;
|
|
886 assert( Formatter( "{}", e ) == "{ key=>true, value=>false }" );
|
|
887
|
|
888 char[][ double ] f;
|
|
889 f[ 1.0 ] = "one".dup;
|
|
890 f[ 3.14 ] = "PI".dup;
|
|
891 assert( Formatter( "{}", f ) == "{ 1.00=>one, 3.14=>PI }" );
|
|
892 }
|
|
893 }
|
|
894
|
|
895
|
|
896
|
|
897 debug (Layout)
|
|
898 {
|
|
899 import tango.io.Console;
|
|
900
|
|
901 void main ()
|
|
902 {
|
|
903 auto layout = new Layout!(char);
|
|
904
|
|
905 Cout (layout ("{:d2}", 56)).newline;
|
|
906 Cout (layout ("{:f4}", 0.001)).newline;
|
|
907 Cout (layout ("{:f8}", 3.14159)).newline;
|
|
908 Cout (layout ("{:e20}", 0.001)).newline;
|
|
909 Cout (layout ("{:e4}", 0.0000001)).newline;
|
|
910 Cout (layout ("ptr:{}", &layout)).newline;
|
|
911
|
|
912 struct S
|
|
913 {
|
|
914 char[] toString () {return "foo";}
|
|
915 }
|
|
916
|
|
917 S s;
|
|
918 Cout (layout ("struct: {}", s)).newline;
|
|
919 }
|
|
920 }
|