288
|
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
|