Mercurial > projects > ldc
comparison 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 |
comparison
equal
deleted
inserted
replaced
131:5825d48b27d1 | 132:1700239cab2e |
---|---|
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 } |