added storage buffer for console output
[dd2d.git] / conwrt.d
bloba22f5f44bfdf20976965eb72267f70b9534c33a3
1 /* DooM2D: Midnight on the Firing Line
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 /* very simple compile-time format writer
19 * understands [+|-]width[.maxlen]
20 * negative width: add spaces to right
21 * negative maxlen: get right part
22 * specifiers:
23 * 's': use to!string to write argument
24 * note that writer can print strings, bools and integrals without allocation
25 * 'S': print asciiz C string
26 * 'x': write integer as hex
27 * 'X': write integer as HEX
28 * '|': write all arguments that's left with "%s"
29 * '@': go to argument with number 'width' (use sign to relative goto)
30 * argument count starts with '1'
31 * '!': skip all arguments that's left, no width allowed
32 * '%': just a percent sign, no width allowed
33 * '$': go to argument with number 'width' (use sign to relative goto), continue parsing
34 * argument count starts with '1'
35 * options (must immediately follow '%'):
36 * '/': center string; negative width means "add extra space (if any) to the right"
37 * '~': fill with the following char instead of space
38 * second '~': right filling char for 'center'
39 * '\0'...'\0': separator string for '%|'
41 module conwrt is aliced;
42 private:
44 import conbuf;
45 private import std.traits : isBoolean, isIntegral, isPointer;
46 static if (!is(typeof(usize))) private alias usize = size_t;
47 private alias StripTypedef(T) = T;
50 __gshared void delegate (scope const(char[])) @trusted nothrow @nogc conwriter;
52 public @property auto ConWriter () @trusted nothrow @nogc { return conwriter; }
53 public @property auto ConWriter (typeof(conwriter) cv) @trusted nothrow @nogc { auto res = conwriter; conwriter = cv; return res; }
56 shared static this () {
57 conwriter = (scope str) @trusted nothrow @nogc {
58 if (str.length > 0) {
59 import core.sys.posix.unistd : STDOUT_FILENO, write;
60 write(STDOUT_FILENO, str.ptr, str.length);
61 cbufPut(str);
67 // width<0: pad right
68 // width == int.min: no width specified
69 // maxlen == int.min: no maxlen specified
70 private void wrWriteWidth(char lfill=' ', char rfill=' ')
71 (int width,
72 int maxlen,
73 bool center,
74 const(char[]) s,
75 bool leftIsMinus=false) {
76 static immutable char[64] spacesl = () { char[64] r; foreach (immutable p; 0..64) r[p] = lfill; return r; }();
77 static immutable char[64] spacesr = () { char[64] r; foreach (immutable p; 0..64) r[p] = rfill; return r; }();
78 usize stpos = 0;
79 // fix maxlen
80 if (maxlen != int.min) {
81 if (maxlen < 0) {
82 maxlen = -maxlen;
83 if (maxlen > s.length) {
84 maxlen = cast(int)s.length;
85 } else {
86 stpos = s.length-maxlen;
88 } else if (maxlen > 0) {
89 if (maxlen > s.length) maxlen = cast(int)s.length;
91 } else {
92 // no maxlen specified
93 maxlen = cast(int)s.length;
95 // fuck overflows
96 if (maxlen < 0) maxlen = 666;
97 // fix width
98 if (width == int.min) {
99 // no width specified, defaults to visible string width
100 width = cast(int)(s.length-stpos);
101 // fuck overflows
102 if (width < 0) width = 666;
104 // centering?
105 if (center && ((width > 0 && width > maxlen) || (width < 0 && -width > maxlen))) {
106 // center string
107 int wdt = (width > 0 ? width : -width)-maxlen;
108 int spleft = wdt/2+(width > 0 && wdt%2);
109 int spright = wdt-spleft;
110 while (spleft > 0) {
111 if (spleft > spacesl.length) {
112 conwriter(spacesl);
113 spleft -= spacesl.length;
114 continue;
115 } else {
116 conwriter(spacesl[0..spleft]);
117 break;
120 if (maxlen > 0) conwriter(s[stpos..stpos+maxlen]);
121 while (spright > 0) {
122 if (spright > spacesr.length) {
123 conwriter(spacesr);
124 spright -= spacesr.length;
125 continue;
126 } else {
127 conwriter(spacesr[0..spright]);
128 break;
131 } else {
132 // pad string
133 bool writeS = true;
134 if (width < 0) {
135 // right padding, write string
136 width = -width;
137 if (maxlen > 0) conwriter(s[stpos..stpos+maxlen]);
138 writeS = false;
140 if (maxlen < width) {
141 width -= maxlen;
142 if (writeS && stpos == 0 && leftIsMinus && width > 0) {
143 conwriter("-");
144 // remove '-'
145 ++stpos;
146 --maxlen;
148 for (;;) {
149 if (width > spacesl.length) {
150 if (writeS) conwriter(spacesl); else conwriter(spacesr);
151 width -= spacesl.length;
152 } else {
153 if (writeS) conwriter(spacesl[0..width]); else conwriter(spacesr[0..width]);
154 break;
158 if (writeS && maxlen > 0) conwriter(s[stpos..stpos+maxlen]);
162 // width<0: pad right
163 // width == int.min: no width specified
164 // maxlen == int.min: no maxlen specified
165 private void wrWriteWidthStrZ(char lfill=' ', char rfill=' ')
166 (int width,
167 int maxlen,
168 bool center,
169 const char *s,
170 bool leftIsMinus=false) {
171 usize end = 0;
172 while (s[end]) ++end;
173 wrWriteWidth!(lfill, rfill)(width, maxlen, center, s[0..end], leftIsMinus);
177 private void wrWriteWidthHex(char lfill=' ', char rfill=' ', T)
178 (int width,
179 int maxlen,
180 bool center,
181 bool upcase,
182 T numm)
183 if (isIntegral!T)
185 import std.traits : isSigned, isMutable, Unqual;
186 static if (isMutable!T) alias num = numm; else Unqual!T num = cast(Unqual!T)numm;
187 char[18] hstr = void;
188 auto pos = hstr.length;
189 static if (isSigned!T) {
190 static if (T.sizeof == 8) {
191 if (num == 0x8000_0000_0000_0000uL) { wrWriteWidth!(lfill, rfill)(width, maxlen, center, "-8000000000000000", (lfill == '0')); return; }
192 } else static if (T.sizeof == 4) {
193 if (num == 0x8000_0000uL) { wrWriteWidth!(lfill, rfill)(width, maxlen, center, "-80000000", (lfill == '0')); return; }
194 } else static if (T.sizeof == 2) {
195 if (num == 0x8000uL) { wrWriteWidth!(lfill, rfill)(width, maxlen, center, "-8000", (lfill == '0')); return; }
196 } else static if (T.sizeof == 1) {
197 if (num == 0x80uL) { wrWriteWidth!(lfill, rfill)(width, maxlen, center, "-80", (lfill == '0')); return; }
199 bool neg = (num < 0);
200 if (neg) num = -num;
202 do {
203 assert(pos > 0);
204 ubyte b = num&0x0f;
205 num >>= 4;
206 if (b < 10) {
207 hstr[--pos] = cast(char)('0'+b);
208 } else if (upcase) {
209 hstr[--pos] = cast(char)('A'+b-10);
210 } else {
211 hstr[--pos] = cast(char)('a'+b-10);
213 } while (num);
214 static if (isSigned!T) {
215 if (neg) {
216 assert(pos > 0);
217 hstr[--pos] = '-';
219 wrWriteWidth!(lfill, rfill)(width, maxlen, center, hstr[pos..$], (neg && lfill == '0'));
220 } else {
221 wrWriteWidth!(lfill, rfill)(width, maxlen, center, hstr[pos..$]);
226 // 2**64: 18446744073709551616 (20 chars)
227 // 2**64: 0x1_0000_0000_0000_0000
228 // width<0: pad right
229 private void wrWriteWidthInt(char lfill=' ', char rfill=' ', T)
230 (int width,
231 int maxlen,
232 bool center,
233 T numm)
234 if (isIntegral!T)
236 import std.traits : isSigned, isMutable, Unqual;
237 static if (isMutable!T) alias num = numm; else Unqual!T num = cast(Unqual!T)numm;
238 char[22] hstr;
239 auto pos = hstr.length;
240 static if (isSigned!T) {
241 static if (T.sizeof == 8) {
242 if (num == 0x8000_0000_0000_0000uL) { wrWriteWidth!(lfill, rfill)(width, maxlen, center, "-9223372036854775808", (lfill == '0')); return; }
243 } else static if (T.sizeof == 4) {
244 if (num == 0x8000_0000uL) { wrWriteWidth!(lfill, rfill)(width, maxlen, center, "-2147483648", (lfill == '0')); return; }
245 } else static if (T.sizeof == 2) {
246 if (num == 0x8000uL) { wrWriteWidth!(lfill, rfill)(width, maxlen, center, "-32768", (lfill == '0')); return; }
247 } else static if (T.sizeof == 1) {
248 if (num == 0x80uL) { wrWriteWidth!(lfill, rfill)(width, maxlen, center, "-128", (lfill == '0')); return; }
250 bool neg = (num < 0);
251 if (neg) num = -num;
253 do {
254 assert(pos > 0);
255 ubyte b = cast(ubyte)(num%10);
256 num /= 10;
257 hstr[--pos] = cast(char)('0'+b);
258 } while (num);
259 static if (isSigned!T) {
260 if (neg) {
261 assert(pos > 0);
262 hstr[--pos] = '-';
264 wrWriteWidth!(lfill, rfill)(width, maxlen, center, hstr[pos..$], (neg && lfill == '0'));
265 } else {
266 wrWriteWidth!(lfill, rfill)(width, maxlen, center, hstr[pos..$]);
271 private void wrWriteWidthBool(char lfill=' ', char rfill=' ', T)
272 (int width,
273 int maxlen,
274 bool center,
275 T v)
276 if (isBoolean!T)
278 wrWriteWidth!(lfill, rfill)(width, maxlen, center, (v ? "true" : "false"));
282 import std.traits : Unqual;
283 private void wrWriteWidthChar(char lfill=' ', char rfill=' ', T)
284 (int width,
285 int maxlen,
286 bool center,
287 T v)
288 if (is(Unqual!T == char))
290 char[1] s = v;
291 wrWriteWidth!(lfill, rfill)(width, maxlen, center, s);
295 ////////////////////////////////////////////////////////////////////////////////
296 private auto WrData (int alen) {
297 static struct Data {
298 int aidx; // current arg index
299 int alen; // number of args
300 // changeable
301 int width = int.min; // this means 'not specified'
302 char widthSign = ' '; // '+', '-', '*' (no sign), ' ' (absent)
303 bool widthZeroStarted;
304 bool widthWasDigits;
305 int maxlen = int.min; // this means 'not specified'
306 char maxlenSign = ' '; // '+', '-', '*' (no sign), ' ' (absent)
307 bool maxlenZeroStarted;
308 bool maxlenWasDigits;
309 bool optCenter; // center string?
310 char lfchar = ' '; // "left fill"
311 char rfchar = ' '; // "right fill"
312 int fillhcharIdx; // 0: next will be lfchar, 1: next will be rfchar; 2: no more fills
313 string wsep; // separator string for "%|"
315 @disable this ();
317 this (usize aalen) {
318 if (aalen >= 1024) assert(0, "too many arguments for writer");
319 alen = cast(int)aalen;
322 // set named field
323 auto set(string name, T) (in T value) if (__traits(hasMember, this, name)) {
324 __traits(getMember, this, name) = value;
325 return this;
328 // increment current index
329 auto incAIdx () {
330 ++aidx;
331 if (aidx > alen) aidx = alen;
332 return this;
335 // prepare for next formatted output (reset all format params)
336 auto resetFmt () {
337 // trick with saving necessary fields
338 auto saidx = aidx;
339 auto salen = alen;
340 this = this.init;
341 aidx = saidx;
342 alen = salen;
343 return this;
346 // set filling char
347 auto setFillChar (char ch) {
348 switch (fillhcharIdx) {
349 case 0: lfchar = ch; break;
350 case 1: rfchar = ch; break;
351 default:
353 ++fillhcharIdx;
354 return this;
357 // prepare to parse integer field
358 auto initInt(string name) (char sign) if (__traits(hasMember, this, name)) {
359 __traits(getMember, this, name) = (sign == '-' ? -1 : 0);
360 __traits(getMember, this, name~"Sign") = sign;
361 __traits(getMember, this, name~"ZeroStarted") = false;
362 __traits(getMember, this, name~"WasDigits") = false;
363 return this;
366 // integer field parsing: process next char
367 auto putIntChar(string name) (char ch) if (__traits(hasMember, this, name)) {
368 bool wd = __traits(getMember, this, name~"WasDigits");
369 if (!wd) {
370 __traits(getMember, this, name~"ZeroStarted") = (ch == '0');
371 __traits(getMember, this, name~"WasDigits") = true;
373 int n = __traits(getMember, this, name);
374 if (n == int.min) n = 0;
375 if (n < 0) {
376 n = -(n+1);
377 immutable nn = n*10+ch-'0';
378 if (nn < n || nn == int.max) assert(0, "integer overflow");
379 n = (-nn)-1;
380 } else {
381 immutable nn = n*10+ch-'0';
382 if (nn < n || nn == int.max) assert(0, "integer overflow");
383 n = nn;
385 __traits(getMember, this, name) = n;
386 return this;
389 //TODO: do more checks on getInt, getBool, etc.
390 auto getInt(string name) () if (__traits(hasMember, this, name)) {
391 import std.traits;
392 immutable n = __traits(getMember, this, name);
393 static if (isSigned!(typeof(n))) {
394 return (n < 0 && n != n.min ? n+1 : n);
395 } else {
396 return n;
400 auto getIntDef(string name) () if (__traits(hasMember, this, name)) {
401 import std.traits;
402 immutable n = __traits(getMember, this, name);
403 static if (isSigned!(typeof(n))) {
404 if (n == n.min) return 0;
405 else if (n < 0) return n+1;
406 else return n;
407 } else {
408 return n;
412 string getIntStr(string name) () if (__traits(hasMember, this, name)) {
413 import std.conv : to;
414 return to!string(getInt!name());
417 string getBoolStr(string name) () if (__traits(hasMember, this, name)) {
418 return (__traits(getMember, this, name) ? "true" : "false");
421 // set fillchar according to width flags
422 auto fixWidthFill () {
423 if (fillhcharIdx == 0 && widthZeroStarted) {
424 lfchar = '0';
425 fillhcharIdx = 1;
427 return this;
431 return Data(alen);
435 ////////////////////////////////////////////////////////////////////////////////
436 // parse (possibly signed) number
437 template conwritefImpl(string state, string field, string fmt, alias data, AA...)
438 if (state == "parse-int")
440 static assert(fmt.length > 0, "invalid format string");
441 static if (fmt[0] == '-' || fmt[0] == '+') {
442 static assert(fmt.length > 1 && fmt[1] >= '0' && fmt[1] <= '9', "invalid number for '"~field~"'");
443 enum conwritefImpl = conwritefImpl!("parse-digits", field, fmt[1..$], data.initInt!field(fmt[0]), AA);
444 } else static if (fmt[0] >= '0' && fmt[0] <= '9') {
445 enum conwritefImpl = conwritefImpl!("parse-digits", field, fmt, data.initInt!field('*'), AA);
446 } else {
447 enum conwritefImpl = conwritefImpl!("got-"~field, fmt, data.initInt!field(' '), AA);
452 // parse integer digits
453 template conwritefImpl(string state, string field, string fmt, alias data, AA...)
454 if (state == "parse-digits")
456 static assert(fmt.length > 0, "invalid format string");
457 static if (fmt[0] >= '0' && fmt[0] <= '9') {
458 enum conwritefImpl = conwritefImpl!(state, field, fmt[1..$], data.putIntChar!field(fmt[0]), AA);
459 } else {
460 enum conwritefImpl = conwritefImpl!("got-"~field, fmt, data, AA);
465 ////////////////////////////////////////////////////////////////////////////////
466 // got maxlen, done with width parsing
467 template conwritefImpl(string state, string fmt, alias data, AA...)
468 if (state == "parse-format")
470 static assert(fmt.length > 0, "invalid format string");
471 static assert(fmt[0] == '%', "internal error");
472 enum conwritefImpl = conwritefImpl!("parse-options", fmt[1..$], data, AA);
476 // parse options
477 template conwritefImpl(string state, string fmt, alias data, AA...)
478 if (state == "parse-options")
480 import std.string : indexOf;
481 static if (fmt[0] == '/') {
482 enum conwritefImpl = conwritefImpl!(state, fmt[1..$], data.set!"optCenter"(true), AA);
483 } else static if (fmt[0] == '~') {
484 static assert(fmt.length > 1, "invalid format option: '~'");
485 enum conwritefImpl = conwritefImpl!(state, fmt[2..$], data.setFillChar(fmt[1]), AA);
486 } else static if (fmt[0] == '\0') {
487 enum epos = fmt.indexOf('\0', 1);
488 static assert(epos > 0, "unterminated separator option");
489 static assert(fmt[epos] == '\0');
490 enum conwritefImpl = conwritefImpl!(state, fmt[epos+1..$], data.set!"wsep"(fmt[1..epos]), AA);
491 } else {
492 enum conwritefImpl = conwritefImpl!("parse-int", "width", fmt, data, AA);
497 // got width, try maxlen
498 template conwritefImpl(string state, string fmt, alias data, AA...)
499 if (state == "got-width")
501 static assert(fmt.length > 0, "invalid format string");
502 static if (fmt[0] == '.') {
503 // got maxlen, parse it
504 enum conwritefImpl = conwritefImpl!("parse-int", "maxlen", fmt[1..$], data.fixWidthFill(), AA);
505 } else {
506 enum conwritefImpl = conwritefImpl!("got-maxlen", fmt, data.fixWidthFill(), AA);
511 // got maxlen, done with width parsing
512 template conwritefImpl(string state, string fmt, alias data, AA...)
513 if (state == "got-maxlen")
515 static assert(fmt.length > 0, "invalid format string");
516 enum conwritefImpl = conwritefImpl!("format-spec", fmt, data, AA);
520 ////////////////////////////////////////////////////////////////////////////////
521 static template isStaticNarrowString(T) {
522 import std.traits : isStaticArray;
523 static if (isStaticArray!T) {
524 import std.traits : Unqual;
525 static alias ArrayElementType(T: T[]) = Unqual!T;
526 enum isStaticNarrowString = is(ArrayElementType!T == char);
527 } else {
528 enum isStaticNarrowString = false;
532 template conwritefImpl(string state, alias data, AA...)
533 if (state == "write-argument-s")
535 import std.traits : Unqual;
536 import std.conv : to;
537 static assert(data.aidx >= 0 && data.aidx < data.alen, "argument index out of range");
538 enum aidx = data.aidx;
539 alias aatype = StripTypedef!(AA[aidx]);
540 //pragma(msg, "TYPE: ", Unqual!aatype);
541 static if (is(Unqual!aatype == char[]) ||
542 is(Unqual!aatype == const(char)[]) ||
543 is(aatype == string) ||
544 isStaticNarrowString!aatype) {
545 //pragma(msg, "STRING!");
546 enum callFunc = "wrWriteWidth";
547 enum func = "";
548 } else static if (isIntegral!aatype) {
549 enum callFunc = "wrWriteWidthInt";
550 enum func = "";
551 } else static if (isBoolean!aatype) {
552 enum callFunc = "wrWriteWidthBool";
553 enum func = "";
554 } else static if (is(Unqual!aatype == char)) {
555 enum callFunc = "wrWriteWidthChar";
556 enum func = "";
557 } else {
558 // this may allocate!
559 enum callFunc = "wrWriteWidth";
560 enum func = "to!string";
562 enum lfchar = data.lfchar;
563 enum rfchar = data.rfchar;
564 enum conwritefImpl =
565 callFunc~"!("~lfchar.stringof~","~rfchar.stringof~")("~
566 data.getIntStr!"width"()~","~
567 data.getIntStr!"maxlen"()~","~
568 data.getBoolStr!"optCenter"()~","~
569 func~"(args["~to!string(aidx)~"]));\n";
573 template conwritefImpl(string state, alias data, AA...)
574 if (state == "write-argument-S")
576 import std.traits : Unqual;
577 import std.conv : to;
578 static assert(data.aidx >= 0 && data.aidx < data.alen, "argument index out of range");
579 enum aidx = data.aidx;
580 alias aatype = StripTypedef!(AA[aidx]);
581 //pragma(msg, "TYPE: ", Unqual!aatype);
582 static if (is(Unqual!aatype == char*) ||
583 is(Unqual!aatype == const(char)*) ||
584 is(Unqual!aatype == immutable(char)*) ||
585 is(Unqual!aatype == const(char*)) ||
586 is(Unqual!aatype == immutable(char*))) {
587 enum lfchar = data.lfchar;
588 enum rfchar = data.rfchar;
589 enum conwritefImpl =
590 "wrWriteWidthStrZ!("~lfchar.stringof~","~rfchar.stringof~")("~
591 data.getIntStr!"width"()~","~
592 data.getIntStr!"maxlen"()~","~
593 data.getBoolStr!"optCenter"()~","~
594 "(cast(const char*)args["~to!string(aidx)~"]));\n";
595 } else {
596 enum conwritefImpl = conwritefImpl!"write-argument-s"(state, data, AA);
601 template conwritefImpl(string state, bool upcase, alias data, AA...)
602 if (state == "write-argument-xx")
604 import std.traits : Unqual;
605 import std.conv : to;
606 static assert(data.aidx >= 0 && data.aidx < data.alen, "argument index out of range");
607 enum aidx = data.aidx;
608 private alias TTA = StripTypedef!(AA[aidx]);
609 static assert(isIntegral!TTA || isPointer!TTA, "'x' expects integer or pointer argument");
610 enum lfchar = data.lfchar;
611 enum rfchar = data.rfchar;
612 enum conwritefImpl =
613 "wrWriteWidthHex!("~lfchar.stringof~","~rfchar.stringof~")("~
614 data.getIntStr!"width"()~","~
615 data.getIntStr!"maxlen"()~","~
616 data.getBoolStr!"optCenter"()~","~
617 (upcase ? "true," : "false,")~
618 (isPointer!TTA ? "cast(usize)" : "cast("~TTA.stringof~")")~
619 "(args["~to!string(aidx)~"]));\n";
623 template conwritefImpl(string state, alias data, AA...)
624 if (state == "write-argument-x")
626 enum conwritefImpl = conwritefImpl!("write-argument-xx", false, data, AA);
630 template conwritefImpl(string state, alias data, AA...)
631 if (state == "write-argument-X")
633 enum conwritefImpl = conwritefImpl!("write-argument-xx", true, data, AA);
637 template conwritefImpl(string state, string field, alias data)
638 if (state == "write-field")
640 enum fld = __traits(getMember, data, field);
641 static if (fld.length > 0) {
642 enum conwritefImpl = "conwriter("~fld.stringof~");\n";
643 } else {
644 enum conwritefImpl = "";
649 template conwritefImpl(string state, string str, alias data)
650 if (state == "write-strlit")
652 static if (str.length > 0) {
653 enum conwritefImpl = "conwriter("~str.stringof~");\n";
654 } else {
655 enum conwritefImpl = "";
660 ////////////////////////////////////////////////////////////////////////////////
661 template conwritefImpl(string state, string fmt, alias data, AA...)
662 if (state == "format-spec")
664 static assert(fmt.length > 0, "invalid format string");
665 static if (fmt[0] == 's' || fmt[0] == 'x' || fmt[0] == 'X' || fmt[0] == 'S') {
666 // known specs
667 enum conwritefImpl =
668 conwritefImpl!("write-argument-"~fmt[0], data, AA)~
669 conwritefImpl!("main", fmt[1..$], data.incAIdx(), AA);
670 } else static if (fmt[0] == '|') {
671 // write all unprocessed arguments
672 static if (data.aidx < data.alen) {
673 // has argument to process
674 static if (data.aidx+1 < data.alen && data.wsep.length > 0) {
675 // has separator
676 enum conwritefImpl =
677 conwritefImpl!("write-argument-s", data, AA)~
678 conwritefImpl!("write-field", "wsep", data)~
679 conwritefImpl!(state, fmt, data.incAIdx(), AA);
680 } else {
681 // has no separator
682 enum conwritefImpl =
683 conwritefImpl!("write-argument-s", data, AA)~
684 conwritefImpl!(state, fmt, data.incAIdx(), AA);
686 } else {
687 // no more arguments
688 enum conwritefImpl = conwritefImpl!("main", fmt[1..$], data, AA);
690 } else static if (fmt[0] == '@' || fmt[0] == '$') {
691 // set current argument index
692 // we must have no maxlen here
693 static assert(data.maxlenSign == ' ', "invalid position for '@'");
694 static if (data.widthSign == '+' || data.widthSign == '-')
695 enum newpos = data.aidx+data.getIntDef!"width"()+1;
696 else
697 enum newpos = data.getIntDef!"width"();
698 static assert(newpos >= 1 && newpos <= data.alen+1, "position out of range for '"~fmt[0]~"'");
699 static if (fmt[0] == '@' || (fmt.length > 1 && fmt[1] == '%')) {
700 enum conwritefImpl = conwritefImpl!("main", fmt[1..$], data.set!"aidx"(newpos-1), AA);
701 } else {
702 enum conwritefImpl = conwritefImpl!("main", "%"~fmt[1..$], data.set!"aidx"(newpos-1), AA);
704 } else {
705 static assert(0, "invalid format specifier: '"~fmt[0]~"'");
710 ////////////////////////////////////////////////////////////////////////////////
711 template conwritefImpl(string state, string accum, string fmt, alias data, AA...)
712 if (state == "main-with-accum")
714 static if (fmt.length == 0) {
715 static assert(data.aidx == data.alen, "too many arguments to writer");
716 enum conwritefImpl = conwritefImpl!("write-strlit", accum, data);
717 } else static if (fmt[0] == '%') {
718 static assert (fmt.length > 1, "invalid format string");
719 static if (fmt[1] == '%') {
720 // '%%'
721 enum conwritefImpl = conwritefImpl!(state, accum~"%", fmt[2..$], data, AA);
722 } else static if (fmt[1] == '!') {
723 // '%!'
724 enum conwritefImpl = conwritefImpl!(state, accum, fmt[2..$], data.set!"aidx"(data.alen), AA);
725 } else {
726 // other format specifiers
727 enum conwritefImpl =
728 conwritefImpl!("write-strlit", accum, data)~
729 conwritefImpl!("parse-format", fmt, data, AA);
731 } else {
732 import std.string : indexOf;
733 enum ppos = fmt.indexOf('%');
734 static if (ppos < 0) {
735 // no format specifiers
736 enum conwritefImpl = conwritefImpl!("write-strlit", accum~fmt, data);
737 } else {
738 enum conwritefImpl = conwritefImpl!(state, accum~fmt[0..ppos], fmt[ppos..$], data, AA);
744 ////////////////////////////////////////////////////////////////////////////////
745 template conwritefImpl(string state, string fmt, alias data, AA...)
746 if (state == "main")
748 enum conwritefImpl = conwritefImpl!("main-with-accum", "", fmt, data.resetFmt(), AA);
752 ////////////////////////////////////////////////////////////////////////////////
753 void fdwritef(string fmt, AA...) (AA args) {
754 import std.string : indexOf;
755 static if (fmt.indexOf('%') < 0) {
756 conwriter(fmt);
757 } else {
758 import std.conv : to;
759 enum mixstr = conwritefImpl!("main", fmt, WrData(AA.length), AA);
760 //pragma(msg, "-------\n"~mixstr~"-------");
761 mixin(mixstr);
763 conwriter(null);
767 ////////////////////////////////////////////////////////////////////////////////
768 public:
770 void conwritef(string fmt, A...) (A args) { fdwritef!(fmt)(args); }
771 void conwritefln(string fmt, A...) (A args) { fdwritef!(fmt~"\n")(args); }
772 void conwrite(A...) (A args) { fdwritef!("%|")(args); }
773 void conwriteln(A...) (A args) { fdwritef!("%|\n")(args); }
776 ////////////////////////////////////////////////////////////////////////////////
777 version(conwriter_test)
778 unittest {
779 class A {
780 override string toString () const { return "{A}"; }
783 char[] n = ['x', 'y', 'z'];
784 char[3] t = "def";//['d', 'e', 'f'];
785 conwriter("========================\n");
786 conwritef!"`%%`\n"();
787 conwritef!"`%-3s`\n"(42);
788 conwritef!"<`%3s`%%{str=%s}%|>\n"(cast(int)42, "[a]", new A(), n, t[]);
789 conwritefln!"<`%3@%3s`>%!"(cast(int)42, "[a]", new A(), n, t);
790 //errwriteln("stderr");
791 conwritefln!"`%-3s`"(42);
792 conwritefln!"`%!z%-2@%-3s`%!"(69, 42, 666);
793 conwritefln!"`%!%1@%-3s%!`"(69, 42, 666);
794 conwritefln!"`%!%-1@%+0@%-3s%!`"(69, 42, 666);
795 conwritefln!"`%3.5s`"("a");
796 conwritefln!"`%7.5s`"("abcdefgh");
797 conwritef!"%|\n"(42, 666);
798 conwritefln!"`%/10.5s`"("abcdefgh");
799 conwritefln!"`%/-10.-5s`"("abcdefgh");
800 conwritefln!"`%/~+-10.-5s`"("abcdefgh");
801 conwritefln!"`%/~+~:-10.-5s`"("abcdefgh");
802 conwritef!"%\0<>\0|\n"(42, 666, 999);
803 conwritef!"%\0\t\0|\n"(42, 666, 999);
804 conwritefln!"`%~*05s %~.5s`"(42, 666);
805 conwritef!"`%s`\n"(t);
806 conwritef!"`%08s`\n"("alice");
807 conwritefln!"#%08x"(16396);
808 conwritefln!"#%08X"(-16396);
809 conwritefln!"#%02X"(-16385);
810 conwritefln!"[%06s]"(-666);
811 conwritefln!"[%06s]"(cast(long)0x8000_0000_0000_0000uL);
812 conwritefln!"[%06x]"(cast(long)0x8000_0000_0000_0000uL);
814 version(aliced) {
815 enum TypedefTestStr = q{
816 typedef MyInt = int;
817 typedef MyString = string;
819 MyInt mi = 42;
820 MyString ms = cast(MyString)"hurry";
821 conwritefln!"%s"(mi);
822 conwritefln!"%x"(mi);
823 conwritefln!"%s"(ms);
825 void testBool () @nogc {
826 conwritefln!"%s"(true);
827 conwritefln!"%s"(false);
829 testBool();
831 conwritefln!"Hello, %2$s, I'm %1$s."("Alice", "Miriel");
832 conwritef!"%2$7s|\n%1$%7s|\n%||\n"("Alice", "Miriel");
834 //mixin(TypedefTestStr);
837 immutable char *strz = "stringz\0s";
838 conwritefln!"[%S]"(strz);
842 ////////////////////////////////////////////////////////////////////////////////
843 mixin template condump (Names...) {
844 auto _xdump_tmp_ = {
845 import conwrt : conwrite;
846 foreach (auto i, auto name; Names) conwrite(name, " = ", mixin(name), (i < Names.length-1 ? ", " : "\n"));
847 return false;
848 }();
851 version(conwriter_test)
852 unittest {
853 int x = 5;
854 int y = 3;
855 int z = 15;
857 mixin condump!("x", "y"); // x = 5, y = 3
858 mixin condump!("z"); // z = 15
859 mixin condump!("x+y"); // x+y = 8
860 mixin condump!("x+y < z"); // x+y < z = true
862 conbufDump();