"r_vsync" console var; slightly faster light tracer
[dd2d.git] / console.d
blobba88c5765387262cc617aa11672ee54d1eb7c7db
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;
554 @property T value(T) () nothrow @nogc {
555 pragma(inline, true);
556 static if (is(T : ulong)) {
557 // integer, xchar, boolean
558 return cast(T)getIntValue;
559 } else static if (is(T : double)) {
560 // floats
561 return cast(T)getDoubleValue;
562 } else static if (is(T : const(char)[])) {
563 // string
564 static if (is(T == string)) return strval.idup;
565 else static if (is(T == char[])) return strval.dup;
566 else return strval;
567 } else {
568 // alas
569 return T.init;
573 @property void value(T) (T val) nothrow {
574 pragma(inline, true);
575 static if (is(T : ulong)) {
576 // integer, xchar, boolean
577 setIntValue(cast(ulong)val);
578 } else static if (is(T : double)) {
579 // floats
580 setDoubleValue(cast(double)val);
581 } else static if (is(T : const(char)[])) {
582 static if (is(T == string)) setStrValue(val); else setCharValue(val);
586 protected:
587 abstract ulong getIntValue () nothrow @nogc;
588 abstract double getDoubleValue () nothrow @nogc;
590 abstract void setIntValue (ulong v) nothrow @nogc;
591 abstract void setDoubleValue (double v) nothrow @nogc;
592 abstract void setStrValue (string v) nothrow;
593 abstract void setCharValue (const(char)[] v) nothrow;
597 // ////////////////////////////////////////////////////////////////////////// //
598 class ConVar(T) : ConVarBase {
599 T* vptr;
600 static if (isIntegral!T) {
601 T minv = T.min;
602 T maxv = T.max;
604 static if (!is(T : const(char)[])) {
605 char[256] vbuf;
608 this (T* avptr, string aname, string ahelp=null) { vptr = avptr; super(aname, ahelp); }
609 static if (isIntegral!T) {
610 this (T* avptr, T aminv, T amaxv, string aname, string ahelp=null) {
611 vptr = avptr;
612 minv = aminv;
613 maxv = amaxv;
614 super(aname, ahelp);
618 override void exec (const(char)[] cmdline) {
619 if (checkHelp(cmdline)) { showHelp; return; }
620 if (!hasArgs(cmdline)) { printValue; return; }
621 static if (is(T == bool)) {
622 while (cmdline.length && cmdline[0] <= 32) cmdline = cmdline[1..$];
623 while (cmdline.length && cmdline[$-1] <= 32) cmdline = cmdline[0..$-1];
624 if (cmdline == "toggle") {
625 *vptr = !(*vptr);
626 return;
629 T val = parseType!T(ref cmdline);
630 if (hasArgs(cmdline)) throw exTooManyArgs;
631 static if (isIntegral!T) {
632 if (val < minv) val = minv;
633 if (val > maxv) val = maxv;
635 *vptr = val;
638 override bool isString () const pure nothrow @nogc {
639 static if (is(T : const(char)[])) {
640 return true;
641 } else {
642 return false;
646 override const(char)[] strval () nothrow @nogc {
647 //conwriteln("*** strval for '", name, "'");
648 import core.stdc.stdio : snprintf;
649 static if (is(T : const(char)[])) {
650 return *vptr;
651 } else static if (is(T == bool)) {
652 return (*vptr ? "tan" : "ona");
653 } else static if (isIntegral!T) {
654 static if (isSigned!T) {
655 auto len = snprintf(vbuf.ptr, vbuf.length, "%lld", cast(long)(*vptr));
656 } else {
657 auto len = snprintf(vbuf.ptr, vbuf.length, "%llu", cast(long)(*vptr));
659 return (len >= 0 ? vbuf[0..len] : "?");
660 } else static if (isFloatingPoint!T) {
661 auto len = snprintf(vbuf.ptr, vbuf.length, "%f", cast(double)(*vptr));
662 return (len >= 0 ? vbuf[0..len] : "?");
666 protected override ulong getIntValue () nothrow @nogc {
667 static if (is(T : ulong) || is(T : double)) return cast(ulong)(*vptr); else return ulong.init;
670 protected override double getDoubleValue () nothrow @nogc {
671 static if (is(T : double) || is(T : ulong)) return cast(double)(*vptr); else return double.init;
674 protected override void setIntValue (ulong v) nothrow @nogc {
675 *vptr = cast(T)v;
678 protected override void setDoubleValue (double v) nothrow @nogc {
679 *vptr = cast(T)v;
682 protected override void setStrValue (string v) nothrow {
683 static if (is(T == string) || is(T == const(char)[])) {
684 *vptr = cast(T)v;
685 } else static if (is(T == char[])) {
686 *vptr = v.dup;
690 protected override void setCharValue (const(char)[] v) nothrow {
691 static if (is(T == string)) *vptr = v.idup;
692 else static if (is(T == const(char)[])) *vptr = v;
693 else static if (is(T == char[])) *vptr = v.dup;
696 override void printValue () {
697 auto wrt = ConWriter;
698 static if (is(T : const(char)[])) {
699 wrt(name);
700 wrt(" ");
701 writeQuotedString(*vptr);
702 wrt("\n");
703 wrt(null); // flush
704 } else static if (is(T == bool)) {
705 conwriteln(name, " ", (*vptr ? "tan" : "ona"));
706 } else {
707 conwriteln(name, " ", *vptr);
713 version(contest_vars) unittest {
714 __gshared int vi = 42;
715 __gshared string vs = "str";
716 __gshared bool vb = true;
717 auto cvi = new ConVar!int(&vi, "vi", "integer variable");
718 auto cvs = new ConVar!string(&vs, "vs", "string variable");
719 auto cvb = new ConVar!bool(&vb, "vb", "bool variable");
720 cvi.exec("?");
721 cvs.exec("?");
722 cvb.exec("?");
723 cvi.exec("");
724 cvs.exec("");
725 cvb.exec("");
726 cvi.exec("666");
727 cvs.exec("'fo\to'");
728 cvi.exec("");
729 cvs.exec("");
730 conwriteln("vi=", vi);
731 conwriteln("vs=[", vs, "]");
732 cvs.exec("'?'");
733 cvs.exec("");
734 cvb.exec("tan");
735 cvb.exec("");
736 cvb.exec("ona");
737 cvb.exec("");
741 void addName (string name) {
742 if (name.length == 0) return;
743 if (name !in cmdlist) {
744 import std.algorithm : sort;
745 //import std.range : array;
746 cmdlistSorted ~= name;
747 cmdlistSorted.sort;
752 public void conRegVar(alias fn, T) (T aminv, T amaxv, string aname, string ahelp=null) if (isIntegral!(typeof(fn)) && isIntegral!T) {
753 if (aname.length == 0) aname = (&fn).stringof[2..$]; // HACK
754 if (aname.length > 0) {
755 addName(aname);
756 cmdlist[aname] = new ConVar!(typeof(fn))(&fn, cast(typeof(fn))aminv, cast(typeof(fn))amaxv, aname, ahelp);
760 public void conRegVar(alias fn) (string aname, string ahelp=null) if (!isCallable!(typeof(fn))) {
761 if (aname.length == 0) aname = (&fn).stringof[2..$]; // HACK
762 if (aname.length > 0) {
763 addName(aname);
764 cmdlist[aname] = new ConVar!(typeof(fn))(&fn, aname, ahelp);
769 // ////////////////////////////////////////////////////////////////////////// //
770 // delegate
771 public class ConFuncBase : ConCommand {
772 this (string aname, string ahelp=null) { super(aname, ahelp); }
776 public struct ConFuncVA {
777 const(char)[] cmdline;
780 // we have to make the class nested, so we can use `dg`, which keeps default args
781 public void conRegFunc(alias fn) (string aname, string ahelp=null) if (isCallable!fn) {
782 // hack for inline lambdas
783 static if (is(typeof(&fn))) {
784 auto dg = &fn;
785 } else {
786 auto dg = fn;
789 class ConFunc : ConFuncBase {
790 this (string aname, string ahelp=null) { super(aname, ahelp); }
792 override void exec (const(char)[] cmdline) {
793 if (checkHelp(cmdline)) { showHelp; return; }
794 Parameters!dg args;
795 static if (args.length == 0) {
796 if (hasArgs(cmdline)) {
797 conwriteln("too many args for command '", name, "'");
798 } else {
799 dg();
801 } else static if (args.length == 1 && is(typeof(args[0]) == ConFuncVA)) {
802 args[0].cmdline = cmdline;
803 dg(args);
804 } else {
805 alias defaultArguments = ParameterDefaultValueTuple!fn;
806 //pragma(msg, "defs: ", defaultArguments);
807 import std.conv : to;
808 foreach (auto idx, ref arg; args) {
809 // populate arguments, with user data if available,
810 // default if not, and throw if no argument provided
811 if (hasArgs(cmdline)) {
812 import std.conv : ConvException;
813 try {
814 arg = parseType!(typeof(arg))(cmdline);
815 } catch (ConvException) {
816 conwriteln("error parsing argument #", idx+1, " for command '", name, "'");
817 return;
819 } else {
820 static if (!is(defaultArguments[idx] == void)) {
821 arg = defaultArguments[idx];
822 } else {
823 conwriteln("required argument #", idx+1, " for command '", name, "' is missing");
824 return;
828 if (hasArgs(cmdline)) {
829 conwriteln("too many args for command '", name, "'");
830 return;
832 //static if (is(ReturnType!dg == void))
833 dg(args);
838 static if (is(typeof(&fn))) {
839 if (aname.length == 0) aname = (&fn).stringof[2..$]; // HACK
841 if (aname.length > 0) {
842 addName(aname);
843 cmdlist[aname] = new ConFunc(aname, ahelp);
848 // ////////////////////////////////////////////////////////////////////////// //
849 __gshared ConCommand[string] cmdlist;
850 __gshared string[] cmdlistSorted;
853 // ////////////////////////////////////////////////////////////////////////// //
854 public bool conHasCommand (const(char)[] name) { pragma(inline, true); return ((name in cmdlist) !is null); }
856 public auto conByCommand () {
857 static struct Range {
858 private:
859 usize idx;
861 public:
862 @property bool empty() () { pragma(inline, true); return (idx >= cmdlistSorted.length); }
863 @property string front() () { pragma(inline, true); return (idx < cmdlistSorted.length ? cmdlistSorted.ptr[idx] : null); }
864 @property bool frontIsVar() () { pragma(inline, true); return (idx < cmdlistSorted.length ? (cast(ConVarBase)cmdlist[cmdlistSorted.ptr[idx]] !is null) : false); }
865 @property bool frontIsFunc() () { pragma(inline, true); return (idx < cmdlistSorted.length ? (cast(ConFuncBase)cmdlist[cmdlistSorted.ptr[idx]] !is null) : false); }
866 void popFront () { pragma(inline, true); if (idx < cmdlistSorted.length) ++idx; }
868 Range res;
869 return res;
873 // ////////////////////////////////////////////////////////////////////////// //
874 public T conGetVar(T) (const(char)[] s) {
875 if (auto cc = s in cmdlist) {
876 if (auto cv = cast(ConVarBase)(*cc)) return cv.value!T;
878 return T.init;
882 public void conSetVar(T) (const(char)[] s, T val) {
883 if (auto cc = s in cmdlist) {
884 if (auto cv = cast(ConVarBase)(*cc)) cv.value = val;
889 // ////////////////////////////////////////////////////////////////////////// //
890 public void conExecute (const(char)[] s) {
891 auto ss = s;
892 try {
893 auto w = ConCommand.getWord(s);
894 if (w is null) return;
895 if (auto cmd = w in cmdlist) {
896 while (s.length && s.ptr[0] <= 32) s = s[1..$];
897 //conwriteln("'", s, "'");
898 (*cmd).exec(s);
899 } else {
900 auto wrt = ConWriter;
901 wrt("command ");
902 ConCommand.writeQuotedString(w);
903 wrt(" not found");
904 wrt("\n");
905 wrt(null); // flush
907 } catch (Exception) {
908 conwriteln("error executing console command:\n ", s);
913 // ////////////////////////////////////////////////////////////////////////// //
914 version(contest_func) unittest {
915 static void xfunc (int v, int x=42) { conwriteln("xfunc: v=", v, "; x=", x); }
917 //pragma(msg, typeof(&xfunc), " ", ParameterDefaultValueTuple!xfunc);
918 conRegFunc!xfunc("", "function with two int args (last has default value '42')");
919 conExecute("xfunc ?");
920 conExecute("xfunc 666");
921 conExecute("xfunc");
923 conRegFunc!({conwriteln("!!!");})("bang");
924 conExecute("bang");
926 conRegFunc!((ConFuncVA va) {
927 int idx = 1;
928 for (;;) {
929 auto w = ConCommand.getWord(va.cmdline);
930 if (w is null) break;
931 conwriteln("#", idx, ": [", w, "]");
932 ++idx;
934 })("doo");
935 conExecute("doo 1 2 ' 34 '");
939 // ////////////////////////////////////////////////////////////////////////// //
940 // return `null` when there is no command
941 public const(char)[] conGetCommand (ref const(char)[] s) {
942 for (;;) {
943 while (s.length > 0 && s[0] <= 32) s = s[1..$];
944 if (s.length == 0) return null;
945 if (s.ptr[0] != ';') break;
946 s = s[1..$];
949 usize pos = 0;
951 void skipString () {
952 char qch = s.ptr[pos++];
953 while (pos < s.length) {
954 if (s.ptr[pos] == qch) { ++pos; break; }
955 if (s.ptr[pos++] == '\\') {
956 if (pos < s.length) {
957 if (s.ptr[pos] == 'x' || s.ptr[pos] == 'X') pos += 2; else ++pos;
963 void skipLine () {
964 while (pos < s.length) {
965 if (s.ptr[pos] == '"' || s.ptr[pos] == '\'') {
966 skipString();
967 } else if (s.ptr[pos++] == '\n') {
968 break;
973 if (s.ptr[0] == '#') {
974 skipLine();
975 if (pos >= s.length) { s = s[$..$]; return null; }
976 s = s[pos..$];
977 pos = 0;
980 while (pos < s.length) {
981 if (s.ptr[pos] == '"' || s.ptr[pos] == '\'') {
982 skipString();
983 } else if (s.ptr[pos] == ';' || s.ptr[pos] == '#' || s.ptr[pos] == '\n') {
984 auto res = s[0..pos];
985 if (s.ptr[pos] == '#') s = s[pos..$]; else s = s[pos+1..$];
986 return res;
987 } else {
988 ++pos;
991 auto res = s[];
992 s = s[$..$];
993 return res;
996 version(contest_cpx) unittest {
997 const(char)[] s = "boo; woo \";\" 42#cmt\ntest\nfoo";
999 auto c = conGetCommand(s);
1000 conwriteln("[", c, "] : [", s, "]");
1003 auto c = conGetCommand(s);
1004 conwriteln("[", c, "] : [", s, "]");
1007 auto c = conGetCommand(s);
1008 conwriteln("[", c, "] : [", s, "]");
1011 auto c = conGetCommand(s);
1012 conwriteln("[", c, "] : [", s, "]");
1017 // ////////////////////////////////////////////////////////////////////////// //
1018 public class ConCommandEcho : ConCommand {
1019 this () { super("echo", "write string to console"); }
1021 override void exec (const(char)[] cmdline) {
1022 if (checkHelp(cmdline)) { showHelp; return; }
1023 if (!hasArgs(cmdline)) return;
1024 bool needSpace = false;
1025 auto wrt = ConWriter;
1026 for (;;) {
1027 auto w = getWord(cmdline);
1028 if (w is null) break;
1029 if (needSpace) wrt(" "); else needSpace = true;
1030 while (w.length) {
1031 usize pos = 0;
1032 while (pos < w.length && w.ptr[pos] != '$') ++pos;
1033 if (w.length-pos > 1 && w.ptr[pos+1] == '$') {
1034 wrt(w[0..pos+1]);
1035 w = w[pos+2..$];
1036 } else if (w.length-pos <= 1) {
1037 wrt(w);
1038 break;
1039 } else {
1040 // variable name
1041 const(char)[] vname;
1042 if (pos > 0) wrt(w[0..pos]);
1043 ++pos;
1044 if (w.ptr[pos] == '{') {
1045 w = w[pos+1..$];
1046 pos = 0;
1047 while (pos < w.length && w.ptr[pos] != '}') ++pos;
1048 vname = w[0..pos];
1049 if (pos < w.length) ++pos;
1050 w = w[pos..$];
1051 } else {
1052 w = w[pos..$];
1053 pos = 0;
1054 while (pos < w.length) {
1055 char ch = w.ptr[pos];
1056 if (ch == '_' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
1057 ++pos;
1058 } else {
1059 break;
1062 vname = w[0..pos];
1063 w = w[pos..$];
1065 if (vname.length) {
1066 if (auto cc = vname in cmdlist) {
1067 if (auto cv = cast(ConVarBase)(*cc)) {
1068 auto v = cv.strval;
1069 wrt(v);
1070 } else {
1071 wrt("${!");
1072 wrt(vname);
1073 wrt("}");
1075 } else {
1076 wrt("${");
1077 wrt(vname);
1078 wrt("}");
1084 wrt("\n");
1085 wrt(null); // flush
1090 shared static this () {
1091 addName("echo");
1092 cmdlist["echo"] = new ConCommandEcho();
1096 public char[] conFormatStr (char[] dest, const(char)[] s) {
1097 usize dpos = 0;
1099 void put (const(char)[] ss) {
1100 if (ss.length == 0) return;
1101 auto len = ss.length;
1102 if (dest.length-dpos < len) len = dest.length-dpos;
1103 if (len) {
1104 dest[dpos..dpos+len] = ss[];
1105 dpos += len;
1109 while (s.length) {
1110 usize pos = 0;
1111 while (pos < s.length && s.ptr[pos] != '$') ++pos;
1112 if (s.length-pos > 1 && s.ptr[pos+1] == '$') {
1113 put(s[0..pos+1]);
1114 s = s[pos+2..$];
1115 } else if (s.length-pos <= 1) {
1116 put(s);
1117 break;
1118 } else {
1119 // variable name
1120 const(char)[] vname;
1121 if (pos > 0) put(s[0..pos]);
1122 ++pos;
1123 if (s.ptr[pos] == '{') {
1124 s = s[pos+1..$];
1125 pos = 0;
1126 while (pos < s.length && s.ptr[pos] != '}') ++pos;
1127 vname = s[0..pos];
1128 if (pos < s.length) ++pos;
1129 s = s[pos..$];
1130 } else {
1131 s = s[pos..$];
1132 pos = 0;
1133 while (pos < s.length) {
1134 char ch = s.ptr[pos];
1135 if (ch == '_' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
1136 ++pos;
1137 } else {
1138 break;
1141 vname = s[0..pos];
1142 s = s[pos..$];
1144 if (vname.length) {
1145 if (auto cc = vname in cmdlist) {
1146 if (auto cv = cast(ConVarBase)(*cc)) {
1147 auto v = cv.strval;
1148 put(v);
1149 } else {
1150 put("${!");
1151 put(vname);
1152 put("}");
1154 } else {
1155 put("${");
1156 put(vname);
1157 put("}");
1162 return dest[0..dpos];
1166 version(contest_echo) unittest {
1167 __gshared int vi = 42;
1168 __gshared string vs = "str";
1169 __gshared bool vb = true;
1170 conRegVar!vi("vi");
1171 conRegVar!vs("vs");
1172 conRegVar!vb("vb");
1173 conRegVar!vb("r_interpolation");
1174 conwriteln("=================");
1175 conExecute("r_interpolation");
1176 conExecute("echo ?");
1177 conExecute("echo vs=$vs, vi=${vi}, vb=${vb}!");
1179 char[44] buf;
1180 auto s = buf.conFormatStr("vs=$vs, vi=${vi}, vb=${vb}!");
1181 conwriteln("[", s, "]");
1182 foreach (auto kv; cmdlist.byKeyValue) conwriteln(" ", kv.key);
1183 assert("r_interpolation" in cmdlist);
1184 s = buf.conFormatStr("Interpolation: $r_interpolation");
1185 conwriteln("[", s, "]");
1190 version(contest_cmdlist) unittest {
1191 auto cl = conByCommand;
1192 while (!cl.empty) {
1193 if (cl.frontIsVar) conwrite("VAR ");
1194 else if (cl.frontIsFunc) conwrite("FUNC ");
1195 else conwrite("UNK ");
1196 conwriteln("[", cl.front, "]");
1197 cl.popFront();