Mercurial > projects > qtd
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 |