smaller "high step" in light tracing
[dd2d.git] / console.d
blob229f4bf9b50b2989abd610ec12f180901e04db20
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 console is aliced;
42 private:
44 private import std.traits : isBoolean, isIntegral, isPointer;
45 static if (!is(typeof(usize))) private alias usize = size_t;
46 private alias StripTypedef(T) = T;
49 __gshared void delegate (scope const(char[]), scope int fd=1) @trusted nothrow @nogc conwriter;
51 public @property auto ConWriter () @trusted nothrow @nogc { return conwriter; }
52 public @property auto ConWriter (typeof(conwriter) cv) @trusted nothrow @nogc { auto res = conwriter; conwriter = cv; return res; }
55 shared static this () {
56 conwriter = (scope str, scope fd) @trusted nothrow @nogc {
57 import core.sys.posix.unistd : STDOUT_FILENO, STDERR_FILENO, write;
58 if (fd >= 0) {
59 if (fd == 0 || fd == 1) fd = STDOUT_FILENO;
60 else if (fd == 2) fd = STDERR_FILENO;
61 if (str.length > 0) write(fd, str.ptr, str.length);
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 fd,
72 int width,
73 int maxlen,
74 bool center,
75 const(char[]) s,
76 bool leftIsMinus=false) {
77 static immutable char[64] spacesl = () { char[64] r; foreach (immutable p; 0..64) r[p] = lfill; return r; }();
78 static immutable char[64] spacesr = () { char[64] r; foreach (immutable p; 0..64) r[p] = rfill; return r; }();
79 usize stpos = 0;
80 // fix maxlen
81 if (maxlen != int.min) {
82 if (maxlen < 0) {
83 maxlen = -maxlen;
84 if (maxlen > s.length) {
85 maxlen = cast(int)s.length;
86 } else {
87 stpos = s.length-maxlen;
89 } else if (maxlen > 0) {
90 if (maxlen > s.length) maxlen = cast(int)s.length;
92 } else {
93 // no maxlen specified
94 maxlen = cast(int)s.length;
96 // fuck overflows
97 if (maxlen < 0) maxlen = 666;
98 // fix width
99 if (width == int.min) {
100 // no width specified, defaults to visible string width
101 width = cast(int)(s.length-stpos);
102 // fuck overflows
103 if (width < 0) width = 666;
105 // centering?
106 if (center && ((width > 0 && width > maxlen) || (width < 0 && -width > maxlen))) {
107 // center string
108 int wdt = (width > 0 ? width : -width)-maxlen;
109 int spleft = wdt/2+(width > 0 && wdt%2);
110 int spright = wdt-spleft;
111 while (spleft > 0) {
112 if (spleft > spacesl.length) {
113 conwriter(spacesl, fd);
114 spleft -= spacesl.length;
115 continue;
116 } else {
117 conwriter(spacesl[0..spleft], fd);
118 break;
121 if (maxlen > 0) conwriter(s[stpos..stpos+maxlen], fd);
122 while (spright > 0) {
123 if (spright > spacesr.length) {
124 conwriter(spacesr, fd);
125 spright -= spacesr.length;
126 continue;
127 } else {
128 conwriter(spacesr[0..spright], fd);
129 break;
132 } else {
133 // pad string
134 bool writeS = true;
135 if (width < 0) {
136 // right padding, write string
137 width = -width;
138 if (maxlen > 0) conwriter(s[stpos..stpos+maxlen], fd);
139 writeS = false;
141 if (maxlen < width) {
142 width -= maxlen;
143 if (writeS && stpos == 0 && leftIsMinus && width > 0) {
144 conwriter("-", fd);
145 // remove '-'
146 ++stpos;
147 --maxlen;
149 for (;;) {
150 if (width > spacesl.length) {
151 if (writeS) conwriter(spacesl, fd); else conwriter(spacesr, fd);
152 width -= spacesl.length;
153 } else {
154 if (writeS) conwriter(spacesl[0..width], fd); else conwriter(spacesr[0..width], fd);
155 break;
159 if (writeS && maxlen > 0) conwriter(s[stpos..stpos+maxlen], fd);
163 // width<0: pad right
164 // width == int.min: no width specified
165 // maxlen == int.min: no maxlen specified
166 private void wrWriteWidthStrZ(char lfill=' ', char rfill=' ')
167 (int fd,
168 int width,
169 int maxlen,
170 bool center,
171 const char *s,
172 bool leftIsMinus=false) {
173 usize end = 0;
174 while (s[end]) ++end;
175 wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, s[0..end], leftIsMinus);
179 private void wrWriteWidthHex(char lfill=' ', char rfill=' ', T)
180 (int fd,
181 int width,
182 int maxlen,
183 bool center,
184 bool upcase,
185 T numm)
186 if (isIntegral!T)
188 import std.traits : isSigned, isMutable, Unqual;
189 static if (isMutable!T) alias num = numm; else Unqual!T num = cast(Unqual!T)numm;
190 char[18] hstr = void;
191 auto pos = hstr.length;
192 static if (isSigned!T) {
193 static if (T.sizeof == 8) {
194 if (num == 0x8000_0000_0000_0000uL) { wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, "-8000000000000000", (lfill == '0')); return; }
195 } else static if (T.sizeof == 4) {
196 if (num == 0x8000_0000uL) { wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, "-80000000", (lfill == '0')); return; }
197 } else static if (T.sizeof == 2) {
198 if (num == 0x8000uL) { wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, "-8000", (lfill == '0')); return; }
199 } else static if (T.sizeof == 1) {
200 if (num == 0x80uL) { wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, "-80", (lfill == '0')); return; }
202 bool neg = (num < 0);
203 if (neg) num = -num;
205 do {
206 assert(pos > 0);
207 ubyte b = num&0x0f;
208 num >>= 4;
209 if (b < 10) {
210 hstr[--pos] = cast(char)('0'+b);
211 } else if (upcase) {
212 hstr[--pos] = cast(char)('A'+b-10);
213 } else {
214 hstr[--pos] = cast(char)('a'+b-10);
216 } while (num);
217 static if (isSigned!T) {
218 if (neg) {
219 assert(pos > 0);
220 hstr[--pos] = '-';
222 wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, hstr[pos..$], (neg && lfill == '0'));
223 } else {
224 wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, hstr[pos..$]);
229 // 2**64: 18446744073709551616 (20 chars)
230 // 2**64: 0x1_0000_0000_0000_0000
231 // width<0: pad right
232 private void wrWriteWidthInt(char lfill=' ', char rfill=' ', T)
233 (int fd,
234 int width,
235 int maxlen,
236 bool center,
237 T numm)
238 if (isIntegral!T)
240 import std.traits : isSigned, isMutable, Unqual;
241 static if (isMutable!T) alias num = numm; else Unqual!T num = cast(Unqual!T)numm;
242 char[22] hstr;
243 auto pos = hstr.length;
244 static if (isSigned!T) {
245 static if (T.sizeof == 8) {
246 if (num == 0x8000_0000_0000_0000uL) { wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, "-9223372036854775808", (lfill == '0')); return; }
247 } else static if (T.sizeof == 4) {
248 if (num == 0x8000_0000uL) { wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, "-2147483648", (lfill == '0')); return; }
249 } else static if (T.sizeof == 2) {
250 if (num == 0x8000uL) { wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, "-32768", (lfill == '0')); return; }
251 } else static if (T.sizeof == 1) {
252 if (num == 0x80uL) { wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, "-128", (lfill == '0')); return; }
254 bool neg = (num < 0);
255 if (neg) num = -num;
257 do {
258 assert(pos > 0);
259 ubyte b = cast(ubyte)(num%10);
260 num /= 10;
261 hstr[--pos] = cast(char)('0'+b);
262 } while (num);
263 static if (isSigned!T) {
264 if (neg) {
265 assert(pos > 0);
266 hstr[--pos] = '-';
268 wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, hstr[pos..$], (neg && lfill == '0'));
269 } else {
270 wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, hstr[pos..$]);
275 private void wrWriteWidthBool(char lfill=' ', char rfill=' ', T)
276 (int fd,
277 int width,
278 int maxlen,
279 bool center,
280 T v)
281 if (isBoolean!T)
283 wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, (v ? "true" : "false"));
287 import std.traits : Unqual;
288 private void wrWriteWidthChar(char lfill=' ', char rfill=' ', T)
289 (int fd,
290 int width,
291 int maxlen,
292 bool center,
293 T v)
294 if (is(Unqual!T == char))
296 char[1] s = v;
297 wrWriteWidth!(lfill, rfill)(fd, width, maxlen, center, s);
301 ////////////////////////////////////////////////////////////////////////////////
302 private auto WrData (int fd, int alen) {
303 static struct Data {
304 string fd; // just to pass to writers; string, 'cause we will concat it with other strings
305 int aidx; // current arg index
306 int alen; // number of args
307 // changeable
308 int width = int.min; // this means 'not specified'
309 char widthSign = ' '; // '+', '-', '*' (no sign), ' ' (absent)
310 bool widthZeroStarted;
311 bool widthWasDigits;
312 int maxlen = int.min; // this means 'not specified'
313 char maxlenSign = ' '; // '+', '-', '*' (no sign), ' ' (absent)
314 bool maxlenZeroStarted;
315 bool maxlenWasDigits;
316 bool optCenter; // center string?
317 char lfchar = ' '; // "left fill"
318 char rfchar = ' '; // "right fill"
319 int fillhcharIdx; // 0: next will be lfchar, 1: next will be rfchar; 2: no more fills
320 string wsep; // separator string for "%|"
322 @disable this ();
324 this (int afd, usize aalen) {
325 import std.conv : to;
326 fd = to!string(afd);
327 if (aalen >= 1024) assert(0, "too many arguments for writer");
328 alen = cast(int)aalen;
331 // set named field
332 auto set(string name, T) (in T value) if (__traits(hasMember, this, name)) {
333 __traits(getMember, this, name) = value;
334 return this;
337 // increment current index
338 auto incAIdx () {
339 ++aidx;
340 if (aidx > alen) aidx = alen;
341 return this;
344 // prepare for next formatted output (reset all format params)
345 auto resetFmt () {
346 // trick with saving necessary fields
347 auto sfd = fd;
348 auto saidx = aidx;
349 auto salen = alen;
350 this = this.init;
351 fd = sfd;
352 aidx = saidx;
353 alen = salen;
354 return this;
357 // set filling char
358 auto setFillChar (char ch) {
359 switch (fillhcharIdx) {
360 case 0: lfchar = ch; break;
361 case 1: rfchar = ch; break;
362 default:
364 ++fillhcharIdx;
365 return this;
368 // prepare to parse integer field
369 auto initInt(string name) (char sign) if (__traits(hasMember, this, name)) {
370 __traits(getMember, this, name) = (sign == '-' ? -1 : 0);
371 __traits(getMember, this, name~"Sign") = sign;
372 __traits(getMember, this, name~"ZeroStarted") = false;
373 __traits(getMember, this, name~"WasDigits") = false;
374 return this;
377 // integer field parsing: process next char
378 auto putIntChar(string name) (char ch) if (__traits(hasMember, this, name)) {
379 bool wd = __traits(getMember, this, name~"WasDigits");
380 if (!wd) {
381 __traits(getMember, this, name~"ZeroStarted") = (ch == '0');
382 __traits(getMember, this, name~"WasDigits") = true;
384 int n = __traits(getMember, this, name);
385 if (n == int.min) n = 0;
386 if (n < 0) {
387 n = -(n+1);
388 immutable nn = n*10+ch-'0';
389 if (nn < n || nn == int.max) assert(0, "integer overflow");
390 n = (-nn)-1;
391 } else {
392 immutable nn = n*10+ch-'0';
393 if (nn < n || nn == int.max) assert(0, "integer overflow");
394 n = nn;
396 __traits(getMember, this, name) = n;
397 return this;
400 //TODO: do more checks on getInt, getBool, etc.
401 auto getInt(string name) () if (__traits(hasMember, this, name)) {
402 import std.traits;
403 immutable n = __traits(getMember, this, name);
404 static if (isSigned!(typeof(n))) {
405 return (n < 0 && n != n.min ? n+1 : n);
406 } else {
407 return n;
411 auto getIntDef(string name) () if (__traits(hasMember, this, name)) {
412 import std.traits;
413 immutable n = __traits(getMember, this, name);
414 static if (isSigned!(typeof(n))) {
415 if (n == n.min) return 0;
416 else if (n < 0) return n+1;
417 else return n;
418 } else {
419 return n;
423 string getIntStr(string name) () if (__traits(hasMember, this, name)) {
424 import std.conv : to;
425 return to!string(getInt!name());
428 string getBoolStr(string name) () if (__traits(hasMember, this, name)) {
429 return (__traits(getMember, this, name) ? "true" : "false");
432 // set fillchar according to width flags
433 auto fixWidthFill () {
434 if (fillhcharIdx == 0 && widthZeroStarted) {
435 lfchar = '0';
436 fillhcharIdx = 1;
438 return this;
442 return Data(fd, alen);
446 ////////////////////////////////////////////////////////////////////////////////
447 // parse (possibly signed) number
448 template conwritefImpl(string state, string field, string fmt, alias data, AA...)
449 if (state == "parse-int")
451 static assert(fmt.length > 0, "invalid format string");
452 static if (fmt[0] == '-' || fmt[0] == '+') {
453 static assert(fmt.length > 1 && fmt[1] >= '0' && fmt[1] <= '9', "invalid number for '"~field~"'");
454 enum conwritefImpl = conwritefImpl!("parse-digits", field, fmt[1..$], data.initInt!field(fmt[0]), AA);
455 } else static if (fmt[0] >= '0' && fmt[0] <= '9') {
456 enum conwritefImpl = conwritefImpl!("parse-digits", field, fmt, data.initInt!field('*'), AA);
457 } else {
458 enum conwritefImpl = conwritefImpl!("got-"~field, fmt, data.initInt!field(' '), AA);
463 // parse integer digits
464 template conwritefImpl(string state, string field, string fmt, alias data, AA...)
465 if (state == "parse-digits")
467 static assert(fmt.length > 0, "invalid format string");
468 static if (fmt[0] >= '0' && fmt[0] <= '9') {
469 enum conwritefImpl = conwritefImpl!(state, field, fmt[1..$], data.putIntChar!field(fmt[0]), AA);
470 } else {
471 enum conwritefImpl = conwritefImpl!("got-"~field, fmt, data, AA);
476 ////////////////////////////////////////////////////////////////////////////////
477 // got maxlen, done with width parsing
478 template conwritefImpl(string state, string fmt, alias data, AA...)
479 if (state == "parse-format")
481 static assert(fmt.length > 0, "invalid format string");
482 static assert(fmt[0] == '%', "internal error");
483 enum conwritefImpl = conwritefImpl!("parse-options", fmt[1..$], data, AA);
487 // parse options
488 template conwritefImpl(string state, string fmt, alias data, AA...)
489 if (state == "parse-options")
491 import std.string : indexOf;
492 static if (fmt[0] == '/') {
493 enum conwritefImpl = conwritefImpl!(state, fmt[1..$], data.set!"optCenter"(true), AA);
494 } else static if (fmt[0] == '~') {
495 static assert(fmt.length > 1, "invalid format option: '~'");
496 enum conwritefImpl = conwritefImpl!(state, fmt[2..$], data.setFillChar(fmt[1]), AA);
497 } else static if (fmt[0] == '\0') {
498 enum epos = fmt.indexOf('\0', 1);
499 static assert(epos > 0, "unterminated separator option");
500 static assert(fmt[epos] == '\0');
501 enum conwritefImpl = conwritefImpl!(state, fmt[epos+1..$], data.set!"wsep"(fmt[1..epos]), AA);
502 } else {
503 enum conwritefImpl = conwritefImpl!("parse-int", "width", fmt, data, AA);
508 // got width, try maxlen
509 template conwritefImpl(string state, string fmt, alias data, AA...)
510 if (state == "got-width")
512 static assert(fmt.length > 0, "invalid format string");
513 static if (fmt[0] == '.') {
514 // got maxlen, parse it
515 enum conwritefImpl = conwritefImpl!("parse-int", "maxlen", fmt[1..$], data.fixWidthFill(), AA);
516 } else {
517 enum conwritefImpl = conwritefImpl!("got-maxlen", fmt, data.fixWidthFill(), AA);
522 // got maxlen, done with width parsing
523 template conwritefImpl(string state, string fmt, alias data, AA...)
524 if (state == "got-maxlen")
526 static assert(fmt.length > 0, "invalid format string");
527 enum conwritefImpl = conwritefImpl!("format-spec", fmt, data, AA);
531 ////////////////////////////////////////////////////////////////////////////////
532 static template isStaticNarrowString(T) {
533 import std.traits : isStaticArray;
534 static if (isStaticArray!T) {
535 import std.traits : Unqual;
536 static alias ArrayElementType(T: T[]) = Unqual!T;
537 enum isStaticNarrowString = is(ArrayElementType!T == char);
538 } else {
539 enum isStaticNarrowString = false;
543 template conwritefImpl(string state, alias data, AA...)
544 if (state == "write-argument-s")
546 import std.traits : Unqual;
547 import std.conv : to;
548 static assert(data.aidx >= 0 && data.aidx < data.alen, "argument index out of range");
549 enum aidx = data.aidx;
550 alias aatype = StripTypedef!(AA[aidx]);
551 //pragma(msg, "TYPE: ", Unqual!aatype);
552 static if (is(Unqual!aatype == char[]) ||
553 is(Unqual!aatype == const(char)[]) ||
554 is(aatype == string) ||
555 isStaticNarrowString!aatype) {
556 //pragma(msg, "STRING!");
557 enum callFunc = "wrWriteWidth";
558 enum func = "";
559 } else static if (isIntegral!aatype) {
560 enum callFunc = "wrWriteWidthInt";
561 enum func = "";
562 } else static if (isBoolean!aatype) {
563 enum callFunc = "wrWriteWidthBool";
564 enum func = "";
565 } else static if (is(Unqual!aatype == char)) {
566 enum callFunc = "wrWriteWidthChar";
567 enum func = "";
568 } else {
569 // this may allocate!
570 enum callFunc = "wrWriteWidth";
571 enum func = "to!string";
573 enum lfchar = data.lfchar;
574 enum rfchar = data.rfchar;
575 enum conwritefImpl =
576 callFunc~"!("~lfchar.stringof~","~rfchar.stringof~")("~
577 data.fd~","~
578 data.getIntStr!"width"()~","~
579 data.getIntStr!"maxlen"()~","~
580 data.getBoolStr!"optCenter"()~","~
581 func~"(args["~to!string(aidx)~"]));\n";
585 template conwritefImpl(string state, alias data, AA...)
586 if (state == "write-argument-S")
588 import std.traits : Unqual;
589 import std.conv : to;
590 static assert(data.aidx >= 0 && data.aidx < data.alen, "argument index out of range");
591 enum aidx = data.aidx;
592 alias aatype = StripTypedef!(AA[aidx]);
593 //pragma(msg, "TYPE: ", Unqual!aatype);
594 static if (is(Unqual!aatype == char*) ||
595 is(Unqual!aatype == const(char)*) ||
596 is(Unqual!aatype == immutable(char)*) ||
597 is(Unqual!aatype == const(char*)) ||
598 is(Unqual!aatype == immutable(char*))) {
599 enum lfchar = data.lfchar;
600 enum rfchar = data.rfchar;
601 enum conwritefImpl =
602 "wrWriteWidthStrZ!("~lfchar.stringof~","~rfchar.stringof~")("~
603 data.fd~","~
604 data.getIntStr!"width"()~","~
605 data.getIntStr!"maxlen"()~","~
606 data.getBoolStr!"optCenter"()~","~
607 "(cast(const char*)args["~to!string(aidx)~"]));\n";
608 } else {
609 enum conwritefImpl = conwritefImpl!"write-argument-s"(state, data, AA);
614 template conwritefImpl(string state, bool upcase, alias data, AA...)
615 if (state == "write-argument-xx")
617 import std.traits : Unqual;
618 import std.conv : to;
619 static assert(data.aidx >= 0 && data.aidx < data.alen, "argument index out of range");
620 enum aidx = data.aidx;
621 private alias TTA = StripTypedef!(AA[aidx]);
622 static assert(isIntegral!TTA || isPointer!TTA, "'x' expects integer or pointer argument");
623 enum lfchar = data.lfchar;
624 enum rfchar = data.rfchar;
625 enum conwritefImpl =
626 "wrWriteWidthHex!("~lfchar.stringof~","~rfchar.stringof~")("~
627 data.fd~","~
628 data.getIntStr!"width"()~","~
629 data.getIntStr!"maxlen"()~","~
630 data.getBoolStr!"optCenter"()~","~
631 (upcase ? "true," : "false,")~
632 (isPointer!TTA ? "cast(usize)" : "cast("~TTA.stringof~")")~
633 "(args["~to!string(aidx)~"]));\n";
637 template conwritefImpl(string state, alias data, AA...)
638 if (state == "write-argument-x")
640 enum conwritefImpl = conwritefImpl!("write-argument-xx", false, data, AA);
644 template conwritefImpl(string state, alias data, AA...)
645 if (state == "write-argument-X")
647 enum conwritefImpl = conwritefImpl!("write-argument-xx", true, data, AA);
651 template conwritefImpl(string state, string field, alias data)
652 if (state == "write-field")
654 enum fld = __traits(getMember, data, field);
655 static if (fld.length > 0) {
656 enum conwritefImpl = "conwriter("~fld.stringof~", "~data.fd~");\n";
657 } else {
658 enum conwritefImpl = "";
663 template conwritefImpl(string state, string str, alias data)
664 if (state == "write-strlit")
666 static if (str.length > 0) {
667 enum conwritefImpl = "conwriter("~str.stringof~", "~data.fd~");\n";
668 } else {
669 enum conwritefImpl = "";
674 ////////////////////////////////////////////////////////////////////////////////
675 template conwritefImpl(string state, string fmt, alias data, AA...)
676 if (state == "format-spec")
678 static assert(fmt.length > 0, "invalid format string");
679 static if (fmt[0] == 's' || fmt[0] == 'x' || fmt[0] == 'X' || fmt[0] == 'S') {
680 // known specs
681 enum conwritefImpl =
682 conwritefImpl!("write-argument-"~fmt[0], data, AA)~
683 conwritefImpl!("main", fmt[1..$], data.incAIdx(), AA);
684 } else static if (fmt[0] == '|') {
685 // write all unprocessed arguments
686 static if (data.aidx < data.alen) {
687 // has argument to process
688 static if (data.aidx+1 < data.alen && data.wsep.length > 0) {
689 // has separator
690 enum conwritefImpl =
691 conwritefImpl!("write-argument-s", data, AA)~
692 conwritefImpl!("write-field", "wsep", data)~
693 conwritefImpl!(state, fmt, data.incAIdx(), AA);
694 } else {
695 // has no separator
696 enum conwritefImpl =
697 conwritefImpl!("write-argument-s", data, AA)~
698 conwritefImpl!(state, fmt, data.incAIdx(), AA);
700 } else {
701 // no more arguments
702 enum conwritefImpl = conwritefImpl!("main", fmt[1..$], data, AA);
704 } else static if (fmt[0] == '@' || fmt[0] == '$') {
705 // set current argument index
706 // we must have no maxlen here
707 static assert(data.maxlenSign == ' ', "invalid position for '@'");
708 static if (data.widthSign == '+' || data.widthSign == '-')
709 enum newpos = data.aidx+data.getIntDef!"width"()+1;
710 else
711 enum newpos = data.getIntDef!"width"();
712 static assert(newpos >= 1 && newpos <= data.alen+1, "position out of range for '"~fmt[0]~"'");
713 static if (fmt[0] == '@' || (fmt.length > 1 && fmt[1] == '%')) {
714 enum conwritefImpl = conwritefImpl!("main", fmt[1..$], data.set!"aidx"(newpos-1), AA);
715 } else {
716 enum conwritefImpl = conwritefImpl!("main", "%"~fmt[1..$], data.set!"aidx"(newpos-1), AA);
718 } else {
719 static assert(0, "invalid format specifier: '"~fmt[0]~"'");
724 ////////////////////////////////////////////////////////////////////////////////
725 template conwritefImpl(string state, string accum, string fmt, alias data, AA...)
726 if (state == "main-with-accum")
728 static if (fmt.length == 0) {
729 static assert(data.aidx == data.alen, "too many arguments to writer");
730 enum conwritefImpl = conwritefImpl!("write-strlit", accum, data);
731 } else static if (fmt[0] == '%') {
732 static assert (fmt.length > 1, "invalid format string");
733 static if (fmt[1] == '%') {
734 // '%%'
735 enum conwritefImpl = conwritefImpl!(state, accum~"%", fmt[2..$], data, AA);
736 } else static if (fmt[1] == '!') {
737 // '%!'
738 enum conwritefImpl = conwritefImpl!(state, accum, fmt[2..$], data.set!"aidx"(data.alen), AA);
739 } else {
740 // other format specifiers
741 enum conwritefImpl =
742 conwritefImpl!("write-strlit", accum, data)~
743 conwritefImpl!("parse-format", fmt, data, AA);
745 } else {
746 import std.string : indexOf;
747 enum ppos = fmt.indexOf('%');
748 static if (ppos < 0) {
749 // no format specifiers
750 enum conwritefImpl = conwritefImpl!("write-strlit", accum~fmt, data);
751 } else {
752 enum conwritefImpl = conwritefImpl!(state, accum~fmt[0..ppos], fmt[ppos..$], data, AA);
758 ////////////////////////////////////////////////////////////////////////////////
759 template conwritefImpl(string state, string fmt, alias data, AA...)
760 if (state == "main")
762 enum conwritefImpl = conwritefImpl!("main-with-accum", "", fmt, data.resetFmt(), AA);
766 ////////////////////////////////////////////////////////////////////////////////
767 void fdwritef(int fd, string fmt, AA...) (AA args) {
768 import std.string : indexOf;
769 static if (fmt.indexOf('%') < 0) {
770 conwriter(fmt, fd);
771 } else {
772 import std.conv : to;
773 enum mixstr = conwritefImpl!("main", fmt, WrData(fd, AA.length), AA);
774 //pragma(msg, "-------\n"~mixstr~"-------");
775 mixin(mixstr);
777 conwriter(null, fd);
781 ////////////////////////////////////////////////////////////////////////////////
782 public:
784 //void fdwritef(int fd, string fmt, A...) (A args) { fdwritef!(fd, fmt)(args); }
785 void fdwrite(int fd, A...) (A args) { fdwritef!(fd, "%|")(args); }
786 void fdwriteln(int fd, A...) (A args) { fdwritef!(fd, "%|\n")(args); }
788 void conwritef(string fmt, A...) (A args) { fdwritef!(1, fmt)(args); }
789 void errwritef(string fmt, A...) (A args) { fdwritef!(2, fmt)(args); }
791 void conwritefln(string fmt, A...) (A args) { fdwritef!(1, fmt~"\n")(args); }
792 void errwritefln(string fmt, A...) (A args) { fdwritef!(2, fmt~"\n")(args); }
794 void conwrite(A...) (A args) { fdwritef!(1, "%|")(args); }
795 void errwrite(A...) (A args) { fdwritef!(2, "%|")(args); }
797 void conwriteln(A...) (A args) { fdwritef!(1, "%|\n")(args); }
798 void errwriteln(A...) (A args) { fdwritef!(2, "%|\n")(args); }
801 ////////////////////////////////////////////////////////////////////////////////
802 version(conwriter_test)
803 unittest {
804 class A {
805 override string toString () const { return "{A}"; }
808 char[] n = ['x', 'y', 'z'];
809 char[3] t = "def";//['d', 'e', 'f'];
810 conwriter("========================\n");
811 conwritef!"`%%`\n"();
812 conwritef!"`%-3s`\n"(42);
813 conwritef!"<`%3s`%%{str=%s}%|>\n"(cast(int)42, "[a]", new A(), n, t[]);
814 conwritefln!"<`%3@%3s`>%!"(cast(int)42, "[a]", new A(), n, t);
815 errwriteln("stderr");
816 conwritefln!"`%-3s`"(42);
817 conwritefln!"`%!z%-2@%-3s`%!"(69, 42, 666);
818 conwritefln!"`%!%1@%-3s%!`"(69, 42, 666);
819 conwritefln!"`%!%-1@%+0@%-3s%!`"(69, 42, 666);
820 conwritefln!"`%3.5s`"("a");
821 conwritefln!"`%7.5s`"("abcdefgh");
822 conwritef!"%|\n"(42, 666);
823 conwritefln!"`%/10.5s`"("abcdefgh");
824 conwritefln!"`%/-10.-5s`"("abcdefgh");
825 conwritefln!"`%/~+-10.-5s`"("abcdefgh");
826 conwritefln!"`%/~+~:-10.-5s`"("abcdefgh");
827 conwritef!"%\0<>\0|\n"(42, 666, 999);
828 conwritef!"%\0\t\0|\n"(42, 666, 999);
829 conwritefln!"`%~*05s %~.5s`"(42, 666);
830 conwritef!"`%s`\n"(t);
831 conwritef!"`%08s`\n"("alice");
832 conwritefln!"#%08x"(16396);
833 conwritefln!"#%08X"(-16396);
834 conwritefln!"#%02X"(-16385);
835 conwritefln!"[%06s]"(-666);
836 conwritefln!"[%06s]"(cast(long)0x8000_0000_0000_0000uL);
837 conwritefln!"[%06x]"(cast(long)0x8000_0000_0000_0000uL);
839 version(aliced) {
840 enum TypedefTestStr = q{
841 typedef MyInt = int;
842 typedef MyString = string;
844 MyInt mi = 42;
845 MyString ms = cast(MyString)"hurry";
846 conwritefln!"%s"(mi);
847 conwritefln!"%x"(mi);
848 conwritefln!"%s"(ms);
850 void testBool () @nogc {
851 conwritefln!"%s"(true);
852 conwritefln!"%s"(false);
854 testBool();
856 conwritefln!"Hello, %2$s, I'm %1$s."("Alice", "Miriel");
857 conwritef!"%2$7s|\n%1$%7s|\n%||\n"("Alice", "Miriel");
859 //mixin(TypedefTestStr);
862 immutable char *strz = "stringz\0s";
863 conwritefln!"[%S]"(strz);
867 ////////////////////////////////////////////////////////////////////////////////
868 mixin template condump (Names...) {
869 auto _xdump_tmp_ = {
870 import console : conwrite;
871 foreach (auto i, auto name; Names) conwrite(name, " = ", mixin(name), (i < Names.length-1 ? ", " : "\n"));
872 return false;
873 }();
876 unittest {
877 int x = 5;
878 int y = 3;
879 int z = 15;
881 mixin condump!("x", "y"); // x = 5, y = 3
882 mixin condump!("z"); // z = 15
883 mixin condump!("x+y"); // x+y = 8
884 mixin condump!("x+y < z"); // x+y < z = true