d2dimage: better png writer
[dd2d.git] / console.d
blob924813a1268a05dd36f05d981f602b8771ecbd37
1 // WARNING! thread-unsafe!
2 module console is aliced;
4 public import conwrt;
5 public import conbuf;
7 private:
8 import std.traits;
11 // ////////////////////////////////////////////////////////////////////////// //
12 public class ConCommand {
13 private:
14 public import std.conv : ConvException, ConvOverflowException;
15 import std.range;
16 // this is hack to avoid allocating error exceptions
17 // don't do this at home!
19 __gshared ConvException exBadNum;
20 __gshared ConvException exBadStr;
21 __gshared ConvException exBadBool;
22 __gshared ConvException exBadInt;
23 __gshared ConvOverflowException exIntOverflow;
24 __gshared ConvException exBadHexEsc;
25 __gshared ConvException exBadEscChar;
26 __gshared ConvException exNoArg;
27 __gshared ConvException exTooManyArgs;
28 __gshared ConvException exBadArgType;
30 shared static this () {
31 exBadNum = new ConvException("invalid number");
32 exBadStr = new ConvException("invalid string");
33 exBadBool = new ConvException("invalid boolean");
34 exBadInt = new ConvException("invalid integer number");
35 exIntOverflow = new ConvOverflowException("overflow in integral conversion");
36 exBadHexEsc = new ConvException("invalid hex escape");
37 exBadEscChar = new ConvException("invalid escape char");
38 exNoArg = new ConvException("argument expected");
39 exTooManyArgs = new ConvException("too many arguments");
40 exBadArgType = new ConvException("can't parse given argument type (internal error)");
43 private:
44 __gshared char[] wordBuf;
46 public:
47 string name;
48 string help;
50 this (string aname, string ahelp=null) { name = aname; help = ahelp; }
52 void showHelp () { conwriteln(name, " -- ", help); }
54 // can throw, yep
55 // cmdline doesn't contain command name
56 void exec (const(char)[] cmdline) {
57 auto w = getWord(cmdline);
58 if (w == "?") showHelp;
61 protected:
62 static:
63 int digit(TC) (TC ch, uint base) pure nothrow @safe @nogc if (isSomeChar!TC) {
64 int res = void;
65 if (ch >= '0' && ch <= '9') res = ch-'0';
66 else if (ch >= 'A' && ch <= 'Z') res = ch-'A'+10;
67 else if (ch >= 'a' && ch <= 'z') res = ch-'a'+10;
68 else return -1;
69 return (res >= base ? -1 : res);
72 // get word from command line
73 // note that next call to `getWord()` can destroy result
74 // returns `null` if there are no more words
75 // `*strtemp` will be `true` if temporary string storage was used
76 const(char)[] getWord (ref const(char)[] s, bool *strtemp=null) {
77 if (strtemp !is null) *strtemp = false;
78 usize pos;
79 while (s.length > 0 && s.ptr[0] <= ' ') s = s[1..$];
80 if (s.length == 0) return null;
81 // quoted string?
82 if (s.ptr[0] == '"' || s.ptr[0] == '\'') {
83 char qch = s.ptr[0];
84 s = s[1..$];
85 pos = 0;
86 bool hasSpecial = false;
87 while (pos < s.length && s.ptr[pos] != qch) {
88 if (s.ptr[pos] == '\\') { hasSpecial = true; break; }
89 ++pos;
91 // simple quoted string?
92 if (!hasSpecial) {
93 auto res = s[0..pos];
94 if (pos < s.length) ++pos; // skip closing quote
95 s = s[pos..$];
96 return res;
98 if (strtemp !is null) *strtemp = true;
99 wordBuf.assumeSafeAppend.length = pos;
100 if (pos) wordBuf[0..pos] = s[0..pos];
101 // process special chars
102 while (pos < s.length && s.ptr[pos] != qch) {
103 if (s.ptr[pos] == '\\' && s.length-pos > 1) {
104 ++pos;
105 switch (s.ptr[pos++]) {
106 case '"': case '\'': case '\\': wordBuf ~= s.ptr[pos-1]; break;
107 case '0': wordBuf ~= '\x00'; break;
108 case 'a': wordBuf ~= '\a'; break;
109 case 'b': wordBuf ~= '\b'; break;
110 case 'e': wordBuf ~= '\x1b'; break;
111 case 'f': wordBuf ~= '\f'; break;
112 case 'n': wordBuf ~= '\n'; break;
113 case 'r': wordBuf ~= '\r'; break;
114 case 't': wordBuf ~= '\t'; break;
115 case 'v': wordBuf ~= '\v'; break;
116 case 'x': case 'X':
117 int n = 0;
118 foreach (immutable _; 0..2) {
119 if (pos >= s.length) throw exBadHexEsc;
120 char c2 = s.ptr[pos++];
121 if (digit(c2, 16) < 0) throw exBadHexEsc;
122 n = n*16+digit(c2, 16);
124 wordBuf ~= cast(char)n;
125 break;
126 default: throw exBadEscChar;
128 continue;
130 wordBuf ~= s.ptr[pos++];
132 if (pos < s.length) ++pos; // skip closing quote
133 s = s[pos..$];
134 return wordBuf;
135 } else {
136 // normal word
137 pos = 0;
138 while (pos < s.length && s.ptr[pos] > ' ') ++pos;
139 auto res = s[0..pos];
140 s = s[pos..$];
141 return res;
145 T parseType(T) (ref const(char)[] s) {
146 import std.utf : byCodeUnit;
147 // number
148 static if (is(T == bool)) {
149 auto w = getWord(s);
150 if (w is null) throw exNoArg;
151 if (w.length > 5) throw exBadBool;
152 char[5] tbuf;
153 usize pos = 0;
154 foreach (char ch; w[]) {
155 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
156 tbuf.ptr[pos++] = ch;
158 w = tbuf[0..w.length];
159 switch (w) {
160 case "y": case "t":
161 case "yes": case "tan":
162 case "true": case "on":
163 case "1":
164 return true;
165 case "n": case "f":
166 case "no": case "ona":
167 case "false": case "off":
168 case "0":
169 return false;
170 default: break;
172 throw exBadBool;
173 } else static if ((isIntegral!T || isFloatingPoint!T) && !is(T == enum)) {
174 auto w = getWord(s);
175 if (w is null) throw exNoArg;
176 auto ss = w.byCodeUnit;
177 auto res = parseNum!T(ss);
178 if (!ss.empty) throw exBadNum;
179 return res;
180 } else static if (is(T : const(char)[])) {
181 bool stemp = false;
182 auto w = getWord(s);
183 if (w is null) throw exNoArg;
184 if (s.length && s.ptr[0] > 32) throw exBadStr;
185 if (stemp) {
186 // temp storage was used
187 static if (is(T == string)) return w.idup; else return w.dup;
188 } else {
189 // no temp storage was used
190 static if (is(T == const(char)[])) return w;
191 else static if (is(T == string)) return w.idup;
192 else return w.dup;
194 } else {
195 throw exBadArgType;
199 /** parse integer number.
201 * parser checks for overflows and understands different bases (0x, 0b, 0o, 0d).
202 * parser skips leading spaces. stops on first non-numeric char.
204 * Params:
205 * T = result type
206 * s = input range; will be modified
208 * Returns:
209 * parsed number
211 * Throws:
212 * ConvException or ConvOverflowException
214 private T parseInt(T, TS) (ref TS s) if (isSomeChar!(ElementType!TS) && isIntegral!T && !is(T == enum)) {
215 import std.traits : isSigned;
217 uint base = 10;
218 ulong num = 0;
219 static if (isSigned!T) bool neg = false;
220 // skip spaces
221 while (!s.empty) {
222 if (s.front > 32) break;
223 s.popFront();
225 if (s.empty) throw exBadInt;
226 // check for sign
227 switch (s.front) {
228 case '+': // it's ok
229 s.popFront();
230 break;
231 case '-':
232 static if (isSigned!T) {
233 neg = true;
234 s.popFront();
235 break;
236 } else {
237 throw exBadInt;
239 default: // do nothing
241 if (s.empty) throw exBadInt;
242 // check for various bases
243 if (s.front == '0') {
244 s.popFront();
245 if (s.empty) return cast(T)0;
246 auto ch = s.front;
247 switch (/*auto ch = s.front*/ch) {
248 case 'b': case 'B': base = 2; goto gotbase;
249 case 'o': case 'O': base = 8; goto gotbase;
250 case 'd': case 'D': base = 10; goto gotbase;
251 case 'x': case 'X': base = 16;
252 gotbase:
253 s.popFront();
254 goto checkfirstdigit;
255 default:
256 if (ch != '_' && digit(ch, base) < 0) throw exBadInt;
257 break;
259 } else {
260 // no base specification; we want at least one digit
261 checkfirstdigit:
262 if (s.empty || digit(s.front, base) < 0) throw exBadInt;
264 // parse number
265 // we already know that the next char is valid
266 bool needDigit = false;
267 do {
268 auto ch = s.front;
269 int d = digit(ch, base);
270 if (d < 0) {
271 if (needDigit) throw exBadInt;
272 if (ch != '_') break;
273 needDigit = true;
274 } else {
275 // funny overflow checks
276 auto onum = num;
277 if ((num *= base) < onum) throw exIntOverflow;
278 if ((num += d) < onum) throw exIntOverflow;
279 needDigit = false;
281 s.popFront();
282 } while (!s.empty);
283 if (needDigit) throw exBadInt;
284 // check underflow and overflow
285 static if (isSigned!T) {
286 long n = cast(long)num;
287 if (neg) {
288 // special case: negative 0x8000_0000_0000_0000uL is ok
289 if (num > 0x8000_0000_0000_0000uL) throw exIntOverflow;
290 if (num != 0x8000_0000_0000_0000uL) n = -n;
291 } else {
292 if (num >= 0x8000_0000_0000_0000uL) throw exIntOverflow;
294 if (n < T.min || n > T.max) throw exIntOverflow;
295 return cast(T)n;
296 } else {
297 if (num < T.min || num > T.max) throw exIntOverflow;
298 return cast(T)num;
302 /** parse number.
304 * parser checks for overflows and understands different integer bases (0x, 0b, 0o, 0d).
305 * parser skips leading spaces. stops on first non-numeric char.
307 * Params:
308 * T = result type
309 * s = input range; will be modified
311 * Returns:
312 * parsed number
314 * Throws:
315 * ConvException or ConvOverflowException
317 private T parseNum(T, TS) (ref TS s) if (isSomeChar!(ElementType!TS) && (isIntegral!T || isFloatingPoint!T) && !is(T == enum)) {
318 static if (isIntegral!T) {
319 return parseInt!T(s);
320 } else {
321 import std.conv : parse;
322 while (!s.empty) {
323 if (s.front > 32) break;
324 s.popFront();
326 return std.conv.parse!T(s);
330 bool checkHelp (const(char)[] s) {
331 usize pos = 0;
332 while (pos < s.length && s.ptr[pos] <= 32) ++pos;
333 if (pos == s.length || s.ptr[pos] != '?') return false;
334 ++pos;
335 while (pos < s.length && s.ptr[pos] <= 32) ++pos;
336 return (pos >= s.length);
339 bool hasArgs (const(char)[] s) {
340 usize pos = 0;
341 while (pos < s.length && s.ptr[pos] <= 32) ++pos;
342 return (pos < s.length);
345 void writeQuotedString (const(char)[] s) {
346 static immutable string hexd = "0123456789abcdef";
347 static bool isBadChar() (char ch) {
348 pragma(inline, true);
349 return (ch < ' ' || ch == '\\' || ch == '"' || ch > 126);
351 auto wrt = ConWriter;
352 wrt("\"");
353 usize pos = 0;
354 while (pos < s.length) {
355 usize end = pos;
356 while (end < s.length && !isBadChar(s.ptr[end])) ++end;
357 if (end > pos) wrt(s[pos..end]);
358 pos = end;
359 if (pos >= s.length) break;
360 wrt("\\");
361 switch (s.ptr[pos++]) {
362 case '"': case '\'': case '\\': wrt(s.ptr[pos-1..pos]); break;
363 case '\x00': wrt("0"); break;
364 case '\a': wrt("a"); break;
365 case '\b': wrt("b"); break;
366 case '\e': wrt("e"); break;
367 case '\f': wrt("f"); break;
368 case '\n': wrt("n"); break;
369 case '\r': wrt("r"); break;
370 case '\t': wrt("t"); break;
371 case '\v': wrt("c"); break;
372 default:
373 ubyte c = cast(ubyte)(s.ptr[pos-1]);
374 wrt("x");
375 wrt(hexd[c>>4..(c>>4)+1]);
376 wrt(hexd[c&0x0f..c&0x0f+1]);
377 break;
380 wrt("\"");
385 version(contest_parser) unittest {
386 auto cc = new ConCommand("!");
387 string s = "this is 'a test' string \"you\tknow\" ";
388 auto sc = cast(const(char)[])s;
390 auto w = cc.getWord(sc);
391 assert(w == "this");
394 auto w = cc.getWord(sc);
395 assert(w == "is");
398 auto w = cc.getWord(sc);
399 assert(w == "a test");
402 auto w = cc.getWord(sc);
403 assert(w == "string");
406 auto w = cc.getWord(sc);
407 assert(w == "you\tknow");
410 auto w = cc.getWord(sc);
411 assert(w is null);
412 assert(sc.length == 0);
415 import std.conv : ConvException, ConvOverflowException;
416 import std.exception;
417 import std.math : abs;
419 void testnum(T) (string s, T res, int line=__LINE__) {
420 import std.string : format;
421 bool ok = false;
422 try {
423 import std.utf : byCodeUnit;
424 auto ss = s.byCodeUnit;
425 auto v = ConCommand.parseNum!T(ss);
426 while (!ss.empty && ss.front <= 32) ss.popFront();
427 if (!ss.empty) throw new ConvException("shit happens!");
428 static assert(is(typeof(v) == T));
429 static if (isIntegral!T) ok = (v == res); else ok = (abs(v-res) < T.epsilon);
430 } catch (ConvException e) {
431 assert(0, format("unexpected exception thrown, called from line %s", line));
433 if (!ok) assert(0, format("assertion failure, called from line %s", line));
436 void testbadnum(T) (string s, int line=__LINE__) {
437 import std.string : format;
438 try {
439 import std.utf : byCodeUnit;
440 auto ss = s.byCodeUnit;
441 auto v = ConCommand.parseNum!T(ss);
442 while (!ss.empty && ss.front <= 32) ss.popFront();
443 if (!ss.empty) throw new ConvException("shit happens!");
444 } catch (ConvException e) {
445 return;
447 assert(0, format("exception not thrown, called from line %s", line));
450 testnum!int(" -42", -42);
451 testnum!int(" +42", 42);
452 testnum!int(" -4_2", -42);
453 testnum!int(" +4_2", 42);
454 testnum!int(" -0d42", -42);
455 testnum!int(" +0d42", 42);
456 testnum!int("0x2a", 42);
457 testnum!int("-0x2a", -42);
458 testnum!int("0o52", 42);
459 testnum!int("-0o52", -42);
460 testnum!int("0b00101010", 42);
461 testnum!int("-0b00101010", -42);
462 testnum!ulong("+9223372036854775808", 9223372036854775808uL);
463 testnum!long("9223372036854775807", 9223372036854775807);
464 testnum!long("-9223372036854775808", -9223372036854775808uL); // uL to workaround issue #13606
465 testnum!ulong("+0x8000_0000_0000_0000", 9223372036854775808uL);
466 testnum!long("-0x8000_0000_0000_0000", -9223372036854775808uL); // uL to workaround issue #13606
467 testbadnum!long("9223372036854775808");
468 testbadnum!int("_42");
469 testbadnum!int("42_");
470 testbadnum!int("42_ ");
471 testbadnum!int("4__2");
472 testbadnum!int("0x_2a");
473 testbadnum!int("-0x_2a");
474 testbadnum!int("_0x2a");
475 testbadnum!int("-_0x2a");
476 testbadnum!int("_00x2a");
477 testbadnum!int("-_00x2a");
478 testbadnum!int(" +0x");
480 testnum!int("666", 666);
481 testnum!int("+666", 666);
482 testnum!int("-666", -666);
484 testbadnum!int("+");
485 testbadnum!int("-");
486 testbadnum!int("5a");
488 testbadnum!int("5.0");
489 testbadnum!int("5e+2");
491 testnum!uint("666", 666);
492 testnum!uint("+666", 666);
493 testbadnum!uint("-666");
495 testnum!int("0x29a", 666);
496 testnum!int("0X29A", 666);
497 testnum!int("-0x29a", -666);
498 testnum!int("-0X29A", -666);
499 testnum!int("0b100", 4);
500 testnum!int("0B100", 4);
501 testnum!int("-0b100", -4);
502 testnum!int("-0B100", -4);
503 testnum!int("0o666", 438);
504 testnum!int("0O666", 438);
505 testnum!int("-0o666", -438);
506 testnum!int("-0O666", -438);
507 testnum!int("0d666", 666);
508 testnum!int("0D666", 666);
509 testnum!int("-0d666", -666);
510 testnum!int("-0D666", -666);
512 testnum!byte("-0x7f", -127);
513 testnum!byte("-0x80", -128);
514 testbadnum!byte("0x80");
515 testbadnum!byte("-0x81");
517 testbadnum!uint("1a");
518 testbadnum!uint("0x1g");
519 testbadnum!uint("0b12");
520 testbadnum!uint("0o78");
521 testbadnum!uint("0d1f");
523 testbadnum!int("0x_2__9_a__");
524 testbadnum!uint("0x_");
526 testnum!ulong("0x8000000000000000", 0x8000000000000000UL);
527 testnum!long("-0x8000000000000000", -0x8000000000000000uL);
528 testbadnum!long("0x8000000000000000");
529 testbadnum!ulong("0x80000000000000001");
530 testbadnum!long("-0x8000000000000001");
532 testbadnum!float("-0O666");
533 testnum!float("0x666p0", 1638.0f);
534 testnum!float("-0x666p0", -1638.0f);
535 testnum!double("+1.1e+2", 110.0);
536 testnum!double("2.4", 2.4);
537 testnum!double("1_2.4", 12.4);
538 testnum!float("42666e-3", 42.666f);
539 testnum!float(" 4.2 ", 4.2);
541 conwriteln("console: parser test passed");
545 // ////////////////////////////////////////////////////////////////////////// //
546 // variable of some type
547 public class ConVarBase : ConCommand {
548 this (string aname, string ahelp=null) { super(aname, ahelp); }
550 abstract void printValue ();
551 abstract bool isString () const pure nothrow @nogc;
552 abstract const(char)[] strval () nothrow @nogc;
556 // ////////////////////////////////////////////////////////////////////////// //
557 class ConVar(T) : ConVarBase {
558 T* vptr;
559 static if (isIntegral!T) {
560 T minv = T.min;
561 T maxv = T.max;
563 static if (!is(T : const(char)[])) {
564 char[256] vbuf;
567 this (T* avptr, string aname, string ahelp=null) { vptr = avptr; super(aname, ahelp); }
568 static if (isIntegral!T) {
569 this (T* avptr, T aminv, T amaxv, string aname, string ahelp=null) {
570 vptr = avptr;
571 minv = aminv;
572 maxv = amaxv;
573 super(aname, ahelp);
577 override void exec (const(char)[] cmdline) {
578 if (checkHelp(cmdline)) { showHelp; return; }
579 if (!hasArgs(cmdline)) { printValue; return; }
580 static if (is(T == bool)) {
581 while (cmdline.length && cmdline[0] <= 32) cmdline = cmdline[1..$];
582 while (cmdline.length && cmdline[$-1] <= 32) cmdline = cmdline[0..$-1];
583 if (cmdline == "toggle") {
584 *vptr = !(*vptr);
585 return;
588 T val = parseType!T(ref cmdline);
589 if (hasArgs(cmdline)) throw exTooManyArgs;
590 static if (isIntegral!T) {
591 if (val < minv) val = minv;
592 if (val > maxv) val = maxv;
594 *vptr = val;
597 override bool isString () const pure nothrow @nogc {
598 static if (is(T : const(char)[])) {
599 return true;
600 } else {
601 return false;
605 override const(char)[] strval () nothrow @nogc {
606 //conwriteln("*** strval for '", name, "'");
607 import core.stdc.stdio : snprintf;
608 static if (is(T : const(char)[])) {
609 return *vptr;
610 } else static if (is(T == bool)) {
611 return (*vptr ? "tan" : "ona");
612 } else static if (isIntegral!T) {
613 static if (isSigned!T) {
614 auto len = snprintf(vbuf.ptr, vbuf.length, "%lld", cast(long)(*vptr));
615 } else {
616 auto len = snprintf(vbuf.ptr, vbuf.length, "%llu", cast(long)(*vptr));
618 return (len >= 0 ? vbuf[0..len] : "?");
619 } else static if (isFloatingPoint!T) {
620 auto len = snprintf(vbuf.ptr, vbuf.length, "%f", cast(double)(*vptr));
621 return (len >= 0 ? vbuf[0..len] : "?");
625 override void printValue () {
626 auto wrt = ConWriter;
627 static if (is(T : const(char)[])) {
628 wrt(name);
629 wrt(" ");
630 writeQuotedString(*vptr);
631 wrt("\n");
632 wrt(null); // flush
633 } else static if (is(T == bool)) {
634 conwriteln(name, " ", (*vptr ? "tan" : "ona"));
635 } else {
636 conwriteln(name, " ", *vptr);
642 version(contest_vars) unittest {
643 __gshared int vi = 42;
644 __gshared string vs = "str";
645 __gshared bool vb = true;
646 auto cvi = new ConVar!int(&vi, "vi", "integer variable");
647 auto cvs = new ConVar!string(&vs, "vs", "string variable");
648 auto cvb = new ConVar!bool(&vb, "vb", "bool variable");
649 cvi.exec("?");
650 cvs.exec("?");
651 cvb.exec("?");
652 cvi.exec("");
653 cvs.exec("");
654 cvb.exec("");
655 cvi.exec("666");
656 cvs.exec("'fo\to'");
657 cvi.exec("");
658 cvs.exec("");
659 conwriteln("vi=", vi);
660 conwriteln("vs=[", vs, "]");
661 cvs.exec("'?'");
662 cvs.exec("");
663 cvb.exec("tan");
664 cvb.exec("");
665 cvb.exec("ona");
666 cvb.exec("");
670 void addName (string name) {
671 if (name.length == 0) return;
672 if (name !in cmdlist) {
673 import std.algorithm : sort;
674 //import std.range : array;
675 cmdlistSorted ~= name;
676 cmdlistSorted.sort;
681 public void conRegVar(alias fn, T) (T aminv, T amaxv, string aname, string ahelp=null) if (isIntegral!(typeof(fn)) && isIntegral!T) {
682 if (aname.length == 0) aname = (&fn).stringof[2..$]; // HACK
683 if (aname.length > 0) {
684 addName(aname);
685 cmdlist[aname] = new ConVar!(typeof(fn))(&fn, cast(typeof(fn))aminv, cast(typeof(fn))amaxv, aname, ahelp);
689 public void conRegVar(alias fn) (string aname, string ahelp=null) if (!isCallable!(typeof(fn))) {
690 if (aname.length == 0) aname = (&fn).stringof[2..$]; // HACK
691 if (aname.length > 0) {
692 addName(aname);
693 cmdlist[aname] = new ConVar!(typeof(fn))(&fn, aname, ahelp);
698 // ////////////////////////////////////////////////////////////////////////// //
699 // delegate
700 public class ConFuncBase : ConCommand {
701 this (string aname, string ahelp=null) { super(aname, ahelp); }
705 public struct ConFuncVA {
706 const(char)[] cmdline;
709 // we have to make the class nested, so we can use `dg`, which keeps default args
710 public void conRegFunc(alias fn) (string aname, string ahelp=null) if (isCallable!fn) {
711 // hack for inline lambdas
712 static if (is(typeof(&fn))) {
713 auto dg = &fn;
714 } else {
715 auto dg = fn;
718 class ConFunc : ConFuncBase {
719 this (string aname, string ahelp=null) { super(aname, ahelp); }
721 override void exec (const(char)[] cmdline) {
722 if (checkHelp(cmdline)) { showHelp; return; }
723 Parameters!dg args;
724 static if (args.length == 0) {
725 if (hasArgs(cmdline)) {
726 conwriteln("too many args for command '", name, "'");
727 } else {
728 dg();
730 } else static if (args.length == 1 && is(typeof(args[0]) == ConFuncVA)) {
731 args[0].cmdline = cmdline;
732 dg(args);
733 } else {
734 alias defaultArguments = ParameterDefaultValueTuple!fn;
735 //pragma(msg, "defs: ", defaultArguments);
736 import std.conv : to;
737 foreach (auto idx, ref arg; args) {
738 // populate arguments, with user data if available,
739 // default if not, and throw if no argument provided
740 if (hasArgs(cmdline)) {
741 import std.conv : ConvException;
742 try {
743 arg = parseType!(typeof(arg))(cmdline);
744 } catch (ConvException) {
745 conwriteln("error parsing argument #", idx+1, " for command '", name, "'");
746 return;
748 } else {
749 static if (!is(defaultArguments[idx] == void)) {
750 arg = defaultArguments[idx];
751 } else {
752 conwriteln("required argument #", idx+1, " for command '", name, "' is missing");
753 return;
757 if (hasArgs(cmdline)) {
758 conwriteln("too many args for command '", name, "'");
759 return;
761 //static if (is(ReturnType!dg == void))
762 dg(args);
767 static if (is(typeof(&fn))) {
768 if (aname.length == 0) aname = (&fn).stringof[2..$]; // HACK
770 if (aname.length > 0) {
771 addName(aname);
772 cmdlist[aname] = new ConFunc(aname, ahelp);
777 // ////////////////////////////////////////////////////////////////////////// //
778 __gshared ConCommand[string] cmdlist;
779 __gshared string[] cmdlistSorted;
782 // ////////////////////////////////////////////////////////////////////////// //
783 public bool conHasCommand (const(char)[] name) { pragma(inline, true); return ((name in cmdlist) !is null); }
785 public auto conByCommand () {
786 static struct Range {
787 private:
788 usize idx;
790 public:
791 @property bool empty() () { pragma(inline, true); return (idx >= cmdlistSorted.length); }
792 @property string front() () { pragma(inline, true); return (idx < cmdlistSorted.length ? cmdlistSorted.ptr[idx] : null); }
793 @property bool frontIsVar() () { pragma(inline, true); return (idx < cmdlistSorted.length ? (cast(ConVarBase)cmdlist[cmdlistSorted.ptr[idx]] !is null) : false); }
794 @property bool frontIsFunc() () { pragma(inline, true); return (idx < cmdlistSorted.length ? (cast(ConFuncBase)cmdlist[cmdlistSorted.ptr[idx]] !is null) : false); }
795 void popFront () { pragma(inline, true); if (idx < cmdlistSorted.length) ++idx; }
797 Range res;
798 return res;
802 // ////////////////////////////////////////////////////////////////////////// //
803 public void conExecute (const(char)[] s) {
804 auto ss = s;
805 try {
806 auto w = ConCommand.getWord(s);
807 if (w is null) return;
808 if (auto cmd = w in cmdlist) {
809 while (s.length && s.ptr[0] <= 32) s = s[1..$];
810 //conwriteln("'", s, "'");
811 (*cmd).exec(s);
812 } else {
813 auto wrt = ConWriter;
814 wrt("command ");
815 ConCommand.writeQuotedString(w);
816 wrt(" not found");
817 wrt("\n");
818 wrt(null); // flush
820 } catch (Exception) {
821 conwriteln("error executing console command:\n ", s);
826 // ////////////////////////////////////////////////////////////////////////// //
827 version(contest_func) unittest {
828 static void xfunc (int v, int x=42) { conwriteln("xfunc: v=", v, "; x=", x); }
830 //pragma(msg, typeof(&xfunc), " ", ParameterDefaultValueTuple!xfunc);
831 conRegFunc!xfunc("", "function with two int args (last has default value '42')");
832 conExecute("xfunc ?");
833 conExecute("xfunc 666");
834 conExecute("xfunc");
836 conRegFunc!({conwriteln("!!!");})("bang");
837 conExecute("bang");
839 conRegFunc!((ConFuncVA va) {
840 int idx = 1;
841 for (;;) {
842 auto w = ConCommand.getWord(va.cmdline);
843 if (w is null) break;
844 conwriteln("#", idx, ": [", w, "]");
845 ++idx;
847 })("doo");
848 conExecute("doo 1 2 ' 34 '");
852 // ////////////////////////////////////////////////////////////////////////// //
853 // return `null` when there is no command
854 public const(char)[] conGetCommand (ref const(char)[] s) {
855 for (;;) {
856 while (s.length > 0 && s[0] <= 32) s = s[1..$];
857 if (s.length == 0) return null;
858 if (s.ptr[0] != ';') break;
859 s = s[1..$];
862 usize pos = 0;
864 void skipString () {
865 char qch = s.ptr[pos++];
866 while (pos < s.length) {
867 if (s.ptr[pos] == qch) { ++pos; break; }
868 if (s.ptr[pos++] == '\\') {
869 if (pos < s.length) {
870 if (s.ptr[pos] == 'x' || s.ptr[pos] == 'X') pos += 2; else ++pos;
876 void skipLine () {
877 while (pos < s.length) {
878 if (s.ptr[pos] == '"' || s.ptr[pos] == '\'') {
879 skipString();
880 } else if (s.ptr[pos++] == '\n') {
881 break;
886 if (s.ptr[0] == '#') {
887 skipLine();
888 if (pos >= s.length) { s = s[$..$]; return null; }
889 s = s[pos..$];
890 pos = 0;
893 while (pos < s.length) {
894 if (s.ptr[pos] == '"' || s.ptr[pos] == '\'') {
895 skipString();
896 } else if (s.ptr[pos] == ';' || s.ptr[pos] == '#' || s.ptr[pos] == '\n') {
897 auto res = s[0..pos];
898 if (s.ptr[pos] == '#') s = s[pos..$]; else s = s[pos+1..$];
899 return res;
900 } else {
901 ++pos;
904 auto res = s[];
905 s = s[$..$];
906 return res;
909 version(contest_cpx) unittest {
910 const(char)[] s = "boo; woo \";\" 42#cmt\ntest\nfoo";
912 auto c = conGetCommand(s);
913 conwriteln("[", c, "] : [", s, "]");
916 auto c = conGetCommand(s);
917 conwriteln("[", c, "] : [", s, "]");
920 auto c = conGetCommand(s);
921 conwriteln("[", c, "] : [", s, "]");
924 auto c = conGetCommand(s);
925 conwriteln("[", c, "] : [", s, "]");
930 // ////////////////////////////////////////////////////////////////////////// //
931 public class ConCommandEcho : ConCommand {
932 this () { super("echo", "write string to console"); }
934 override void exec (const(char)[] cmdline) {
935 if (checkHelp(cmdline)) { showHelp; return; }
936 if (!hasArgs(cmdline)) return;
937 bool needSpace = false;
938 auto wrt = ConWriter;
939 for (;;) {
940 auto w = getWord(cmdline);
941 if (w is null) break;
942 if (needSpace) wrt(" "); else needSpace = true;
943 while (w.length) {
944 usize pos = 0;
945 while (pos < w.length && w.ptr[pos] != '$') ++pos;
946 if (w.length-pos > 1 && w.ptr[pos+1] == '$') {
947 wrt(w[0..pos+1]);
948 w = w[pos+2..$];
949 } else if (w.length-pos <= 1) {
950 wrt(w);
951 break;
952 } else {
953 // variable name
954 const(char)[] vname;
955 if (pos > 0) wrt(w[0..pos]);
956 ++pos;
957 if (w.ptr[pos] == '{') {
958 w = w[pos+1..$];
959 pos = 0;
960 while (pos < w.length && w.ptr[pos] != '}') ++pos;
961 vname = w[0..pos];
962 if (pos < w.length) ++pos;
963 w = w[pos..$];
964 } else {
965 w = w[pos..$];
966 pos = 0;
967 while (pos < w.length) {
968 char ch = w.ptr[pos];
969 if (ch == '_' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
970 ++pos;
971 } else {
972 break;
975 vname = w[0..pos];
976 w = w[pos..$];
978 if (vname.length) {
979 if (auto cc = vname in cmdlist) {
980 if (auto cv = cast(ConVarBase)(*cc)) {
981 auto v = cv.strval;
982 wrt(v);
983 } else {
984 wrt("${!");
985 wrt(vname);
986 wrt("}");
988 } else {
989 wrt("${");
990 wrt(vname);
991 wrt("}");
997 wrt("\n");
998 wrt(null); // flush
1003 shared static this () {
1004 addName("echo");
1005 cmdlist["echo"] = new ConCommandEcho();
1009 public char[] conFormatStr (char[] dest, const(char)[] s) {
1010 usize dpos = 0;
1012 void put (const(char)[] ss) {
1013 if (ss.length == 0) return;
1014 auto len = ss.length;
1015 if (dest.length-dpos < len) len = dest.length-dpos;
1016 if (len) {
1017 dest[dpos..dpos+len] = ss[];
1018 dpos += len;
1022 while (s.length) {
1023 usize pos = 0;
1024 while (pos < s.length && s.ptr[pos] != '$') ++pos;
1025 if (s.length-pos > 1 && s.ptr[pos+1] == '$') {
1026 put(s[0..pos+1]);
1027 s = s[pos+2..$];
1028 } else if (s.length-pos <= 1) {
1029 put(s);
1030 break;
1031 } else {
1032 // variable name
1033 const(char)[] vname;
1034 if (pos > 0) put(s[0..pos]);
1035 ++pos;
1036 if (s.ptr[pos] == '{') {
1037 s = s[pos+1..$];
1038 pos = 0;
1039 while (pos < s.length && s.ptr[pos] != '}') ++pos;
1040 vname = s[0..pos];
1041 if (pos < s.length) ++pos;
1042 s = s[pos..$];
1043 } else {
1044 s = s[pos..$];
1045 pos = 0;
1046 while (pos < s.length) {
1047 char ch = s.ptr[pos];
1048 if (ch == '_' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
1049 ++pos;
1050 } else {
1051 break;
1054 vname = s[0..pos];
1055 s = s[pos..$];
1057 if (vname.length) {
1058 if (auto cc = vname in cmdlist) {
1059 if (auto cv = cast(ConVarBase)(*cc)) {
1060 auto v = cv.strval;
1061 put(v);
1062 } else {
1063 put("${!");
1064 put(vname);
1065 put("}");
1067 } else {
1068 put("${");
1069 put(vname);
1070 put("}");
1075 return dest[0..dpos];
1079 version(contest_echo) unittest {
1080 __gshared int vi = 42;
1081 __gshared string vs = "str";
1082 __gshared bool vb = true;
1083 conRegVar!vi("vi");
1084 conRegVar!vs("vs");
1085 conRegVar!vb("vb");
1086 conRegVar!vb("r_interpolation");
1087 conwriteln("=================");
1088 conExecute("r_interpolation");
1089 conExecute("echo ?");
1090 conExecute("echo vs=$vs, vi=${vi}, vb=${vb}!");
1092 char[44] buf;
1093 auto s = buf.conFormatStr("vs=$vs, vi=${vi}, vb=${vb}!");
1094 conwriteln("[", s, "]");
1095 foreach (auto kv; cmdlist.byKeyValue) conwriteln(" ", kv.key);
1096 assert("r_interpolation" in cmdlist);
1097 s = buf.conFormatStr("Interpolation: $r_interpolation");
1098 conwriteln("[", s, "]");
1103 version(contest_cmdlist) unittest {
1104 auto cl = conByCommand;
1105 while (!cl.empty) {
1106 if (cl.frontIsVar) conwrite("VAR ");
1107 else if (cl.frontIsFunc) conwrite("FUNC ");
1108 else conwrite("UNK ");
1109 conwriteln("[", cl.front, "]");
1110 cl.popFront();