1 // WARNING! thread-unsafe!
2 module console
is aliced
;
11 // ////////////////////////////////////////////////////////////////////////// //
12 public class ConCommand
{
14 public import std
.conv
: ConvException
, ConvOverflowException
;
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)");
44 __gshared
char[] wordBuf
;
50 this (string aname
, string ahelp
=null) { name
= aname
; help
= ahelp
; }
52 void showHelp () { conwriteln(name
, " -- ", help
); }
55 // cmdline doesn't contain command name
56 void exec (const(char)[] cmdline
) {
57 auto w
= getWord(cmdline
);
58 if (w
== "?") showHelp
;
63 int digit(TC
) (TC ch
, uint base
) pure nothrow @safe @nogc if (isSomeChar
!TC
) {
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;
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;
79 while (s
.length
> 0 && s
.ptr
[0] <= ' ') s
= s
[1..$];
80 if (s
.length
== 0) return null;
82 if (s
.ptr
[0] == '"' || s
.ptr
[0] == '\'') {
86 bool hasSpecial
= false;
87 while (pos
< s
.length
&& s
.ptr
[pos
] != qch
) {
88 if (s
.ptr
[pos
] == '\\') { hasSpecial
= true; break; }
91 // simple quoted string?
94 if (pos
< s
.length
) ++pos
; // skip closing quote
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) {
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;
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
;
126 default: throw exBadEscChar
;
130 wordBuf
~= s
.ptr
[pos
++];
132 if (pos
< s
.length
) ++pos
; // skip closing quote
138 while (pos
< s
.length
&& s
.ptr
[pos
] > ' ') ++pos
;
139 auto res
= s
[0..pos
];
145 T
parseType(T
) (ref const(char)[] s
) {
146 import std
.utf
: byCodeUnit
;
148 static if (is(T
== bool)) {
150 if (w
is null) throw exNoArg
;
151 if (w
.length
> 5) throw exBadBool
;
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
];
161 case "yes": case "tan":
162 case "true": case "on":
166 case "no": case "ona":
167 case "false": case "off":
173 } else static if ((isIntegral
!T || isFloatingPoint
!T
) && !is(T
== enum)) {
175 if (w
is null) throw exNoArg
;
176 auto ss
= w
.byCodeUnit
;
177 auto res
= parseNum
!T(ss
);
178 if (!ss
.empty
) throw exBadNum
;
180 } else static if (is(T
: const(char)[])) {
183 if (w
is null) throw exNoArg
;
184 if (s
.length
&& s
.ptr
[0] > 32) throw exBadStr
;
186 // temp storage was used
187 static if (is(T
== string
)) return w
.idup
; else return w
.dup
;
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
;
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.
206 * s = input range; will be modified
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
;
219 static if (isSigned
!T
) bool neg = false;
222 if (s
.front
> 32) break;
225 if (s
.empty
) throw exBadInt
;
232 static if (isSigned
!T
) {
239 default: // do nothing
241 if (s
.empty
) throw exBadInt
;
242 // check for various bases
243 if (s
.front
== '0') {
245 if (s
.empty
) return cast(T
)0;
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;
254 goto checkfirstdigit
;
256 if (ch
!= '_' && digit(ch
, base
) < 0) throw exBadInt
;
260 // no base specification; we want at least one digit
262 if (s
.empty ||
digit(s
.front
, base
) < 0) throw exBadInt
;
265 // we already know that the next char is valid
266 bool needDigit
= false;
269 int d
= digit(ch
, base
);
271 if (needDigit
) throw exBadInt
;
272 if (ch
!= '_') break;
275 // funny overflow checks
277 if ((num
*= base
) < onum
) throw exIntOverflow
;
278 if ((num
+= d
) < onum
) throw exIntOverflow
;
283 if (needDigit
) throw exBadInt
;
284 // check underflow and overflow
285 static if (isSigned
!T
) {
286 long n
= cast(long)num
;
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
;
292 if (num
>= 0x8000_0000_0000_0000uL) throw exIntOverflow
;
294 if (n
< T
.min || n
> T
.max
) throw exIntOverflow
;
297 if (num
< T
.min || num
> T
.max
) throw exIntOverflow
;
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.
309 * s = input range; will be modified
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
);
321 import std
.conv
: parse
;
323 if (s
.front
> 32) break;
326 return std
.conv
.parse
!T(s
);
330 bool checkHelp (const(char)[] s
) {
332 while (pos
< s
.length
&& s
.ptr
[pos
] <= 32) ++pos
;
333 if (pos
== s
.length || s
.ptr
[pos
] != '?') return false;
335 while (pos
< s
.length
&& s
.ptr
[pos
] <= 32) ++pos
;
336 return (pos
>= s
.length
);
339 bool hasArgs (const(char)[] s
) {
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
;
354 while (pos
< s
.length
) {
356 while (end
< s
.length
&& !isBadChar(s
.ptr
[end
])) ++end
;
357 if (end
> pos
) wrt(s
[pos
..end
]);
359 if (pos
>= s
.length
) break;
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;
373 ubyte c
= cast(ubyte)(s
.ptr
[pos
-1]);
375 wrt(hexd
[c
>>4..(c
>>4)+1]);
376 wrt(hexd
[c
&0x0f..c
&0x0f+1]);
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
);
394 auto w
= cc
.getWord(sc
);
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
);
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
;
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
;
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
) {
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);
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)) {
561 return cast(T
)getDoubleValue
;
562 } else static if (is(T
: const(char)[])) {
564 static if (is(T
== string
)) return strval
.idup
;
565 else static if (is(T
== char[])) return strval
.dup
;
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)) {
580 setDoubleValue(cast(double)val
);
581 } else static if (is(T
: const(char)[])) {
582 static if (is(T
== string
)) setStrValue(val
); else setCharValue(val
);
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
{
600 static if (isIntegral
!T
) {
604 static if (!is(T
: const(char)[])) {
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) {
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") {
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
;
638 override bool isString () const pure nothrow @nogc {
639 static if (is(T
: const(char)[])) {
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)[])) {
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
));
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 {
678 protected override void setDoubleValue (double v
) nothrow @nogc {
682 protected override void setStrValue (string v
) nothrow {
683 static if (is(T
== string
) ||
is(T
== const(char)[])) {
685 } else static if (is(T
== char[])) {
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)[])) {
701 writeQuotedString(*vptr
);
704 } else static if (is(T
== bool)) {
705 conwriteln(name
, " ", (*vptr ?
"tan" : "ona"));
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");
730 conwriteln("vi=", vi
);
731 conwriteln("vs=[", vs
, "]");
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
;
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) {
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) {
764 cmdlist
[aname
] = new ConVar
!(typeof(fn
))(&fn
, aname
, ahelp
);
769 // ////////////////////////////////////////////////////////////////////////// //
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
))) {
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; }
795 static if (args
.length
== 0) {
796 if (hasArgs(cmdline
)) {
797 conwriteln("too many args for command '", name
, "'");
801 } else static if (args
.length
== 1 && is(typeof(args
[0]) == ConFuncVA
)) {
802 args
[0].cmdline
= cmdline
;
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
;
814 arg
= parseType
!(typeof(arg
))(cmdline
);
815 } catch (ConvException
) {
816 conwriteln("error parsing argument #", idx
+1, " for command '", name
, "'");
820 static if (!is(defaultArguments
[idx
] == void)) {
821 arg
= defaultArguments
[idx
];
823 conwriteln("required argument #", idx
+1, " for command '", name
, "' is missing");
828 if (hasArgs(cmdline
)) {
829 conwriteln("too many args for command '", name
, "'");
832 //static if (is(ReturnType!dg == void))
838 static if (is(typeof(&fn
))) {
839 if (aname
.length
== 0) aname
= (&fn
).stringof
[2..$]; // HACK
841 if (aname
.length
> 0) {
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
{
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
; }
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
;
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
) {
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, "'");
900 auto wrt
= ConWriter
;
902 ConCommand
.writeQuotedString(w
);
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");
923 conRegFunc
!({conwriteln("!!!");})("bang");
926 conRegFunc
!((ConFuncVA va
) {
929 auto w
= ConCommand
.getWord(va
.cmdline
);
930 if (w
is null) break;
931 conwriteln("#", idx
, ": [", w
, "]");
935 conExecute("doo 1 2 ' 34 '");
939 // ////////////////////////////////////////////////////////////////////////// //
940 // return `null` when there is no command
941 public const(char)[] conGetCommand (ref const(char)[] s
) {
943 while (s
.length
> 0 && s
[0] <= 32) s
= s
[1..$];
944 if (s
.length
== 0) return null;
945 if (s
.ptr
[0] != ';') break;
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
;
964 while (pos
< s
.length
) {
965 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
967 } else if (s
.ptr
[pos
++] == '\n') {
973 if (s
.ptr
[0] == '#') {
975 if (pos
>= s
.length
) { s
= s
[$..$]; return null; }
980 while (pos
< s
.length
) {
981 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
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..$];
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
;
1027 auto w
= getWord(cmdline
);
1028 if (w
is null) break;
1029 if (needSpace
) wrt(" "); else needSpace
= true;
1032 while (pos
< w
.length
&& w
.ptr
[pos
] != '$') ++pos
;
1033 if (w
.length
-pos
> 1 && w
.ptr
[pos
+1] == '$') {
1036 } else if (w
.length
-pos
<= 1) {
1041 const(char)[] vname
;
1042 if (pos
> 0) wrt(w
[0..pos
]);
1044 if (w
.ptr
[pos
] == '{') {
1047 while (pos
< w
.length
&& w
.ptr
[pos
] != '}') ++pos
;
1049 if (pos
< w
.length
) ++pos
;
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')) {
1066 if (auto cc
= vname
in cmdlist
) {
1067 if (auto cv
= cast(ConVarBase
)(*cc
)) {
1090 shared static this () {
1092 cmdlist
["echo"] = new ConCommandEcho();
1096 public char[] conFormatStr (char[] dest
, const(char)[] s
) {
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
;
1104 dest
[dpos
..dpos
+len
] = ss
[];
1111 while (pos
< s
.length
&& s
.ptr
[pos
] != '$') ++pos
;
1112 if (s
.length
-pos
> 1 && s
.ptr
[pos
+1] == '$') {
1115 } else if (s
.length
-pos
<= 1) {
1120 const(char)[] vname
;
1121 if (pos
> 0) put(s
[0..pos
]);
1123 if (s
.ptr
[pos
] == '{') {
1126 while (pos
< s
.length
&& s
.ptr
[pos
] != '}') ++pos
;
1128 if (pos
< s
.length
) ++pos
;
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')) {
1145 if (auto cc
= vname
in cmdlist
) {
1146 if (auto cv
= cast(ConVarBase
)(*cc
)) {
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;
1173 conRegVar
!vb("r_interpolation");
1174 conwriteln("=================");
1175 conExecute("r_interpolation");
1176 conExecute("echo ?");
1177 conExecute("echo vs=$vs, vi=${vi}, vb=${vb}!");
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
;
1193 if (cl
.frontIsVar
) conwrite("VAR ");
1194 else if (cl
.frontIsFunc
) conwrite("FUNC ");
1195 else conwrite("UNK ");
1196 conwriteln("[", cl
.front
, "]");