comparison qt/qtd/ctfe/Format.d @ 288:f9559a957be9 signals

new signals and slots implementation
author eldar
date Sun, 08 Nov 2009 19:28:01 +0000
parents
children
comparison
equal deleted inserted replaced
287:b6984b290e46 288:f9559a957be9
1 /**
2 * Compile-Time String Formatting.
3 *
4 * Authors: Daniel Keep <daniel.keep@gmail.com>
5 * Copyright: See LICENSE.
6 */
7 module qt.qtd.ctfe.Format;
8
9 //debug = gb_Format_verbose;
10
11 import Integer = qt.qtd.ctfe.Integer;
12 import String = qt.qtd.ctfe.String;
13 import Tuple = qt.qtd.util.Tuple;
14
15 private
16 {
17 string stringify(Args...)(size_t index, int alignment,
18 string opt, Args args)
19 {
20 if( index >= args.length )
21 return "{invalid index " ~ Integer.format_ctfe(index) ~ "}";
22
23 if( alignment != 0 )
24 return "{non-zero alignments not supported yet}";
25
26 foreach( i,_ ; Args )
27 {
28 if( i == index )
29 {
30 static if( is( Args[i] == char ) )
31 {
32 string r;
33 r ~= args[i];
34 return r;
35 }
36 else static if( is( Args[i] : long ) || is( Args[i] : ulong ) )
37 {
38 int base = 10;
39 string prefix = "";
40
41 if( opt == "x" )
42 base = 16;
43
44 else if( opt == "xx" )
45 {
46 base = 16;
47 prefix = "0x";
48 }
49 else if( opt == "o" )
50 base = 8;
51
52 else if( opt == "b" )
53 base = 2;
54
55 return prefix ~ Integer.format_ctfe(args[i], base);
56 }
57 else static if( is( Args[i] : string ) )
58 {
59 if( opt == "x" )
60 {
61 return String.hexify_ctfe(args[i][]);
62 }
63
64 if( opt == "q" )
65 {
66 return String.escape_ctfe(args[i][]);
67 }
68
69 if( opt == "l" )
70 {
71 return Integer.format_ctfe(args[i].length);
72 }
73
74 // If you don't slice, then the CALLER has to slice the
75 // string, otherwise CTFE barfs.
76 return args[i][];
77 }
78 else static if( is( Args[i] Elem : Elem[] ) )
79 {
80 if( opt == "l" )
81 {
82 return Integer.format_ctfe(args[i].length);
83 }
84
85 string r = "[";
86 foreach( ei, e ; args[i][] )
87 {
88 if( ei != 0 )
89 r ~= ", ";
90 r ~= stringify(0, alignment, opt, e);
91 }
92 r ~= "]";
93 return r;
94 }
95 else
96 {
97 return "{cannot stringify "~Args[i].stringof~"}";
98 }
99 }
100 }
101
102 assert(false);
103 }
104
105 version( Unittest )
106 {
107 static assert( stringify(0, 0, "", 0) == "0" );
108 static assert( stringify(0, 0, "", 1, -2, "abc") == "1" );
109 static assert( stringify(1, 0, "", 1, -2, "abc") == "-2" );
110 static assert( stringify(2, 0, "", 1, -2, "abc") == "abc" );
111
112 static assert( stringify(0, 0, "x", "abc") == `616263` );
113 static assert( stringify(0, 0, "q", "abc") == `"abc"` );
114 static assert( stringify(0, 0, "l", "abc") == `3` );
115
116 static assert( stringify(0, 0, "x", 0x4a) == "4a" );
117
118 static assert( stringify(0, 0, "", [1,2,3]) == "[1, 2, 3]" );
119 static assert( stringify(0, 0, "l", [1,2,3]) == "3" );
120 static assert( stringify(0, 0, "x", [9,10]) == "[9, a]" );
121 static assert( stringify(0, 0, "", ["a","b"]) == "[a, b]" );
122 static assert( stringify(0, 0, "q", ["a","b"]) == "[\"a\", \"b\"]" );
123
124 static assert( stringify(0, 0, "", 'a') == "a" );
125 }
126 }
127
128 /**
129 * Substitutes a set of arguments into a template string.
130 *
131 * The template string allows for the following escape forms:
132 *
133 * - $$ -- Literal dollar.
134 * - $* -- Next argument.
135 * - $n -- nth argument; 0-9 only.
136 * - ${} -- Next argument.
137 * - ${:f} -- Next argument, using format options "f".
138 * - ${n} -- nth argument.
139 * - ${n:f} -- nth argument, using format options "f".
140 *
141 * formatNamed allows the use of named arguments (given as alternating
142 * name,value pairs), but disallows "next" argument and indexed forms.
143 *
144 * Eventually, alignment and named arguments will be supported.
145 *
146 * Supported formatting options are:
147 *
148 * Integers:
149 * - x -- format integer in hexadecimal.
150 * - o -- format integer in octal.
151 * - b -- format integer in binary.
152 *
153 * Strings:
154 * - q -- quotes the string as a literal.
155 * - x -- formats as hexadecimal data.
156 * - l -- length of the string in decimal.
157 *
158 * Arrays:
159 * - l -- length of the array in decimal.
160 * - Other options are used to control element formatting.
161 *
162 * Params:
163 * tmpl = template string.
164 * args = arguments to substitute.
165 * Returns:
166 * formatted string.
167 */
168
169 string format_ctfe(Args...)(string tmpl, Args args)
170 {
171 string r = "";
172 int argPos = 0;
173
174 while( tmpl.length > 0 )
175 {
176 bool inExp = false;
177
178 // Look for a $
179 foreach( i,c ; tmpl )
180 {
181 if (c == '$')
182 {
183 inExp = true;
184 r ~= tmpl[0..i];
185 tmpl = tmpl[i+1..$];
186 break;
187 }
188 }
189
190 // If we didn't find a $, it's because we hit the end of the template.
191 if( !inExp )
192 {
193 r ~= tmpl;
194 break;
195 }
196
197 // So we're in an expansion/substitution.
198
199 debug(gb_Format_verbose) r ~= "{in exp}";
200
201 if( tmpl.length == 0 )
202 {
203 r ~= "{unterminated substitution}";
204 break;
205 }
206
207 // c is the next character, whilst tmpl is everything left in the
208 // template string.
209 char c = tmpl[0];
210 tmpl = tmpl[1..$];
211
212 // $$ - escaped $.
213 if( c == '$' )
214 {
215 debug(gb_Format_verbose) r ~= "{escaped $}";
216 r ~= '$';
217 continue;
218 }
219
220 // $n - shortcut for ${n}.
221 if( '0' <= c && c <= '9' )
222 {
223 debug(gb_Format_verbose) r ~= "{shorthand index}";
224 r ~= stringify(c-'0', 0, "", args);
225 continue;
226 }
227
228 // $* - shortcut for ${}
229 if( c == '*' )
230 {
231 debug(gb_Format_verbose) r ~= "{shorthand next}";
232 r ~= stringify(argPos++, 0, "", args);
233 continue;
234 }
235
236 // This means we got a $ followed by something unexpected.
237 if( c != '{' )
238 {
239 r ~= "{malformed substitution}";
240 break;
241 }
242
243 if( tmpl.length == 0 )
244 {
245 r ~= "{unterminated substitution}";
246 break;
247 }
248
249 debug(gb_Format_verbose)
250 {
251 r ~= "{parse complex at '";
252 r ~= c;
253 r ~= "':\"" ~ tmpl ~ "\"}";
254 }
255
256 // NOTE: We haven't updated c and tmpl yet.
257
258 {
259 // arg will contain the index of the argument the user wanted
260 // substituted.
261 size_t arg = size_t.max;
262 // fmt will contain any additional formatting options.
263 string fmt = "";
264
265 // If we didn't get a : or }, that means we expect an index.
266 if( !( tmpl[0] == ':' || tmpl[0] == '}' ) )
267 {
268 // So parse it.
269 auto used = Integer.parse_ctfe!(size_t)(tmpl, true);
270
271 if( used == 0 )
272 {
273 debug(gb_Format_verbose) r ~= "{used zero of \""~tmpl~"\"}";
274 r ~= "{invalid argument index}";
275 break;
276 }
277
278 arg = Integer.parse_ctfe!(size_t)(tmpl);
279 tmpl = tmpl[used..$];
280
281 if( tmpl.length == 0 )
282 {
283 r ~= "{unterminated substitution}";
284 break;
285 }
286 }
287 else
288 {
289 // Otherwise, the index was elided, which means we want to use
290 // the index of the "next" argument.
291 arg = argPos;
292 ++ argPos;
293 }
294
295 c = tmpl[0];
296 tmpl = tmpl[1..$];
297
298 debug(gb_Format_verbose)
299 r ~= "{index " ~ Integer.format_ctfe(arg) ~ "}";
300
301 // If c is :, then we've got formatting options to parse
302
303 if( c == ':' )
304 {
305 debug(gb_Format_verbose) r ~= "{fmt string}";
306
307 // Look for the closing }.
308 size_t len = 0;
309 foreach( i,d ; tmpl )
310 {
311 if( d == '}' )
312 {
313 len = i;
314 break;
315 }
316 }
317 if( len == 0 )
318 {
319 r ~= "{malformed format}";
320 break;
321 }
322 fmt = tmpl[0..len];
323 tmpl = tmpl[len..$];
324
325 if( tmpl.length == 0 )
326 {
327 r ~= "{unterminated substitution}";
328 break;
329 }
330
331 c = tmpl[0];
332 tmpl = tmpl[1..$];
333 }
334
335 // At this point, we should have the closing }. If not, someone's
336 // screwed up.
337 if( c != '}' )
338 {
339 debug(gb_Format_verbose)
340 {
341 r ~= "{expected closing; got '";
342 r ~= c;
343 r ~= "':\"" ~ tmpl ~ "\"}";
344 }
345 r ~= "{malformed substitution}";
346 break;
347 }
348
349 // Stringify that bugger.
350 r ~= stringify(arg, 0, fmt, args);
351
352 // When we fall off the end here, we'll continue with the
353 // remainder of tmpl, unless it's empty in which case we're
354 // finished.
355 }
356 }
357
358 return r;
359 }
360
361 version( Unittest )
362 {
363 static assert(format_ctfe("A: $$", "foo"[]) == "A: $");
364 static assert(format_ctfe("B: a $$ c", "b"[]) == "B: a $ c");
365
366 static assert(format_ctfe("C: ${}", "foo"[]) == "C: foo");
367 static assert(format_ctfe("D: a ${} c", "b"[]) == "D: a b c");
368
369 static assert(format_ctfe("E: $0", "foo"[]) == "E: foo");
370 static assert(format_ctfe("F: a $0 c", "b"[]) == "F: a b c");
371
372 static assert(format_ctfe("G: $*", "foo"[]) == "G: foo");
373 static assert(format_ctfe("H: a $* c", "b"[]) == "H: a b c");
374
375 static assert(format_ctfe("I: ${0}", "foo"[]) == "I: foo");
376 static assert(format_ctfe("J: a ${0} c", "b"[]) == "J: a b c");
377
378 static assert(format_ctfe("K: ${} ${} ${}", 1, -2, "c"[]) == "K: 1 -2 c");
379 static assert(format_ctfe("L: $* $* $*", 1, -2, "c"[]) == "L: 1 -2 c");
380 static assert(format_ctfe("M: $0 $1 $2", 1, -2, "c"[]) == "M: 1 -2 c");
381 static assert(format_ctfe("N: ${0} ${1} ${2}", 1, -2, "c"[]) == "N: 1 -2 c");
382
383 static assert(format_ctfe("O: ${2} ${0} ${1}", 1, -2, "c"[]) == "O: c 1 -2");
384
385 static assert(format_ctfe("P: ${:x} ${0:x} ${0:o} ${0:b}", 42) == "P: 2a 2a 52 101010");
386
387 static assert(format_ctfe("Q: ${0} ${0:q} ${0:x}", "abc"[]) == "Q: abc \"abc\" 616263");
388 static assert(format_ctfe("R: ${0} ${0:q}", ["a","b","c"][]) == "R: [a, b, c] [\"a\", \"b\", \"c\"]");
389
390 const TORTURE_TMPL = `
391 struct $*Enum
392 {
393 const Name = ${0:q};
394 const string[${:l}] Members = ${1:q};
395
396 ${2} value()
397 {
398 return ${3:xx};
399 }
400 }
401 `[];
402
403 const TORTURE_EXPECTED = `
404 struct FooEnum
405 {
406 const Name = "Foo";
407 const string[3] Members = ["bar", "quxx", "zyzzy"];
408
409 int value()
410 {
411 return 0x42;
412 }
413 }
414 `[];
415
416 const TORTURE_ACTUAL = format_ctfe(TORTURE_TMPL,
417 "Foo"[], ["bar"[],"quxx","zyzzy"][],
418 "int"[], 0x42);
419
420 static assert( TORTURE_EXPECTED == TORTURE_ACTUAL );
421 }
422
423 private
424 {
425 size_t findIndexByName(Args...)(string name, Args args)
426 {
427 foreach( i ; Tuple.Sequence!(0, Args.length, 2) )
428 {
429 static if( !is( Args[i] : string ) )
430 {
431 static assert(false, "expected string for argument "
432 ~ Integer.format_ctfe(i) ~ " in " ~ Args.stringof
433 ~ " not " ~ Args[i].stringof);
434 }
435 if( name == args[i][] )
436 return i+1;
437 }
438 return size_t.max;
439 }
440
441 version( Unittest )
442 {
443 static assert( findIndexByName("a", "a", 0, "b", 1) == 1 );
444 static assert( findIndexByName("b", "a", 0, "b", 1) == 3 );
445 static assert( findIndexByName("c", "a", 0, "b", 1) == size_t.max );
446 }
447 }
448
449 /// ditto
450
451 string formatNamed_ctfe(Args...)(string tmpl, Args args)
452 {
453 string r = "";
454 int argPos = 0;
455
456 while( tmpl.length > 0 )
457 {
458 bool inExp = false;
459
460 // Look for a $
461 foreach( i,c ; tmpl )
462 {
463 if (c == '$')
464 {
465 inExp = true;
466 r ~= tmpl[0..i];
467 tmpl = tmpl[i+1..$];
468 break;
469 }
470 }
471
472 // If we didn't find a $, it's because we hit the end of the template.
473 if( !inExp )
474 {
475 r ~= tmpl;
476 break;
477 }
478
479 // So we're in an expansion/substitution.
480
481 debug(gb_Format_verbose) r ~= "{in exp}";
482
483 if( tmpl.length == 0 )
484 {
485 r ~= "{unterminated substitution}";
486 break;
487 }
488
489 // c is the next character, whilst tmpl is everything left in the
490 // template string.
491 char c = tmpl[0];
492 tmpl = tmpl[1..$];
493
494 // $$ - escaped $.
495 if( c == '$' )
496 {
497 debug(gb_Format_verbose) r ~= "{escaped $}";
498 r ~= '$';
499 continue;
500 }
501
502 // $a... - shortcut for $a...
503 if( String.isIdentStartChar_ctfe(c) )
504 {
505 debug(gb_Format_verbose) r ~= "{shorthand name}";
506 size_t i = 0;
507 while( i < tmpl.length )
508 {
509 if( !String.isIdentChar_ctfe(tmpl[i]) )
510 break;
511 ++ i;
512 }
513 string name = c ~ tmpl[0..i];
514 tmpl = tmpl[i..$];
515 r ~= stringify(findIndexByName(name, args), 0, "", args);
516 continue;
517 }
518
519 // This means we got a $ followed by something unexpected.
520 if( c != '{' )
521 {
522 r ~= "{malformed substitution}";
523 break;
524 }
525
526 if( tmpl.length == 0 )
527 {
528 r ~= "{unterminated substitution}";
529 break;
530 }
531
532 debug(gb_Format_verbose)
533 {
534 r ~= "{parse complex at '";
535 r ~= c;
536 r ~= "':\"" ~ tmpl ~ "\"}";
537 }
538
539 // NOTE: We haven't updated c and tmpl yet.
540
541 {
542 // arg will contain the index of the argument the user wanted
543 // substituted.
544 size_t arg = size_t.max;
545 // fmt will contain any additional formatting options.
546 string fmt = "";
547
548 // If we didn't get a : or }, that means we expect a name.
549 if( !( tmpl[0] == ':' || tmpl[0] == '}' ) )
550 {
551 // So parse it.
552 size_t i = 0;
553 while( i < tmpl.length )
554 {
555 if( !String.isIdentChar_ctfe(tmpl[i]) )
556 break;
557 ++ i;
558 }
559 string name = tmpl[0..i];
560 tmpl = tmpl[i..$];
561
562 arg = findIndexByName(name, args);
563
564 if( tmpl.length == 0 )
565 {
566 r ~= "{unterminated substitution}";
567 break;
568 }
569 }
570 else
571 {
572 // Otherwise, the name was elided. Kaboom!
573 r ~= "{substitution missing name}";
574 break;
575 }
576
577 c = tmpl[0];
578 tmpl = tmpl[1..$];
579
580 debug(gb_Format_verbose)
581 r ~= "{index " ~ Integer.format_ctfe(arg) ~ "}";
582
583 // If c is :, then we've got formatting options to parse
584
585 if( c == ':' )
586 {
587 debug(gb_Format_verbose) r ~= "{fmt string}";
588
589 // Look for the closing }.
590 size_t len = 0;
591 foreach( i,d ; tmpl )
592 {
593 if( d == '}' )
594 {
595 len = i;
596 break;
597 }
598 }
599 if( len == 0 )
600 {
601 r ~= "{malformed format}";
602 break;
603 }
604 fmt = tmpl[0..len];
605 tmpl = tmpl[len..$];
606
607 debug(gb_Format_verbose) r ~= "{fmt:"~fmt~"}";
608
609 if( tmpl.length == 0 )
610 {
611 r ~= "{unterminated substitution}";
612 break;
613 }
614
615 c = tmpl[0];
616 tmpl = tmpl[1..$];
617 }
618
619 // At this point, we should have the closing }. If not, someone's
620 // screwed up.
621 if( c != '}' )
622 {
623 debug(gb_Format_verbose)
624 {
625 r ~= "{expected closing; got '";
626 r ~= c;
627 r ~= "':\"" ~ tmpl ~ "\"}";
628 }
629 r ~= "{malformed substitution}";
630 break;
631 }
632
633 // Stringify that bugger.
634 r ~= stringify(arg, 0, fmt, args);
635
636 // When we fall off the end here, we'll continue with the
637 // remainder of tmpl, unless it's empty in which case we're
638 // finished.
639 }
640 }
641
642 return r;
643 }
644
645 version( Unittest )
646 {
647 static assert( formatNamed_ctfe("A: $$", "a"[], 0, "b"[], 1) == "A: $" );
648 static assert( formatNamed_ctfe("B: $a", "a"[], 0, "b"[], 1) == "B: 0" );
649 static assert( formatNamed_ctfe("C: $b", "a"[], 0, "b"[], 1) == "C: 1" );
650
651 static assert( formatNamed_ctfe("D: ${a}", "a"[], 0, "b"[], 1) == "D: 0" );
652 static assert( formatNamed_ctfe("E: ${b}", "a"[], 0, "b"[], 1) == "E: 1" );
653
654 static assert( formatNamed_ctfe("F: $foo$bar", "foo"[], 0, "bar"[], 1) == "F: 01" );
655 static assert( formatNamed_ctfe("G: ${foo}${bar}", "foo"[], 0, "bar"[], 1) == "G: 01" );
656
657 static assert( formatNamed_ctfe("H: ${foo:x}${bar:xx}", "foo"[], 0, "bar"[], 1) == "H: 00x1" );
658
659 const TORTURE_NAMED_TMPL = `
660 struct ${name}Enum
661 {
662 const Name = ${name:q};
663 const string[${members:l}] Members = ${members:q};
664
665 ${retType} value()
666 {
667 return ${value:xx};
668 }
669 }
670 `[];
671
672 const TORTURE_NAMED_EXPECTED = `
673 struct FooEnum
674 {
675 const Name = "Foo";
676 const string[3] Members = ["bar", "quxx", "zyzzy"];
677
678 int value()
679 {
680 return 0x42;
681 }
682 }
683 `[];
684
685 const TORTURE_NAMED_ACTUAL = formatNamed_ctfe(TORTURE_NAMED_TMPL,
686 "name"[], "Foo"[],
687 "members"[], ["bar"[],"quxx","zyzzy"][],
688 "retType"[], "int"[],
689 "value"[], 0x42);
690
691 static assert( TORTURE_NAMED_EXPECTED == TORTURE_NAMED_ACTUAL );
692 }
693