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 (bool full
=true);
554 // ////////////////////////////////////////////////////////////////////////// //
555 class ConVar(T
) : ConVarBase
{
557 static if (isIntegral
!T
) {
562 this (T
* avptr
, string aname
, string ahelp
=null) { vptr
= avptr
; super(aname
, ahelp
); }
563 static if (isIntegral
!T
) {
564 this (T
* avptr
, T aminv
, T amaxv
, string aname
, string ahelp
=null) {
572 override void exec (const(char)[] cmdline
) {
573 if (checkHelp(cmdline
)) { showHelp
; return; }
574 if (!hasArgs(cmdline
)) { printValue
; return; }
575 static if (is(T
== bool)) {
576 while (cmdline
.length
&& cmdline
[0] <= 32) cmdline
= cmdline
[1..$];
577 while (cmdline
.length
&& cmdline
[$-1] <= 32) cmdline
= cmdline
[0..$-1];
578 if (cmdline
== "toggle") {
583 T val
= parseType
!T(ref cmdline
);
584 if (hasArgs(cmdline
)) throw exTooManyArgs
;
585 static if (isIntegral
!T
) {
586 if (val
< minv
) val
= minv
;
587 if (val
> maxv
) val
= maxv
;
592 override void printValue (bool full
=true) {
593 auto wrt
= ConWriter
;
594 static if (is(T
: const(char)[])) {
598 writeQuotedString(*vptr
);
604 } else static if (is(T
== bool)) {
605 if (!full
) wrt(*vptr ?
"tan" : "ona"); else conwriteln(name
, " ", (*vptr ?
"tan" : "ona"));
607 //TODO: don't flush here
608 if (!full
) conwrite(*vptr
); else conwriteln(name
, " ", *vptr
);
614 version(contest_vars
) unittest {
615 __gshared
int vi
= 42;
616 __gshared string vs
= "str";
617 __gshared
bool vb
= true;
618 auto cvi
= new ConVar
!int(&vi
, "vi", "integer variable");
619 auto cvs
= new ConVar
!string(&vs
, "vs", "string variable");
620 auto cvb
= new ConVar
!bool(&vb
, "vb", "bool variable");
631 conwriteln("vi=", vi
);
632 conwriteln("vs=[", vs
, "]");
642 public void conRegVar(alias fn
, T
) (T aminv
, T amaxv
, string aname
, string ahelp
=null) if (isIntegral
!(typeof(fn
)) && isIntegral
!T
) {
643 if (aname
.length
== 0) aname
= (&fn
).stringof
[2..$]; // HACK
644 if (aname
.length
> 0) cmdlist
[aname
] = new ConVar
!(typeof(fn
))(&fn
, cast(typeof(fn
))aminv
, cast(typeof(fn
))amaxv
, aname
, ahelp
);
647 public void conRegVar(alias fn
) (string aname
, string ahelp
=null) if (!isCallable
!(typeof(fn
))) {
648 if (aname
.length
== 0) aname
= (&fn
).stringof
[2..$]; // HACK
649 if (aname
.length
> 0) cmdlist
[aname
] = new ConVar
!(typeof(fn
))(&fn
, aname
, ahelp
);
653 // ////////////////////////////////////////////////////////////////////////// //
655 public class ConFuncBase
: ConCommand
{
656 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); }
660 // we have to make the class nested, so we can use `dg`, which keeps default args
661 public void conRegFunc(alias fn
) (string aname
, string ahelp
=null) if (isCallable
!fn
) {
662 // hack for inline lambdas
663 static if (is(typeof(&fn
))) {
669 class ConFunc
: ConFuncBase
{
670 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); }
672 override void exec (const(char)[] cmdline
) {
673 if (checkHelp(cmdline
)) { showHelp
; return; }
675 static if (args
.length
== 0) {
676 if (hasArgs(cmdline
)) {
677 conwriteln("too many args for command '", name
, "'");
682 alias defaultArguments
= ParameterDefaultValueTuple
!fn
;
683 pragma(msg
, "defs: ", defaultArguments
);
684 import std
.conv
: to
;
685 foreach (auto idx
, ref arg
; args
) {
686 // populate arguments, with user data if available,
687 // default if not, and throw if no argument provided
688 if (hasArgs(cmdline
)) {
689 import std
.conv
: ConvException
;
691 arg
= parseType
!(typeof(arg
))(cmdline
);
692 } catch (ConvException
) {
693 conwriteln("error parsing argument #", idx
+1, " for command '", name
, "'");
697 static if (!is(defaultArguments
[idx
] == void)) {
698 arg
= defaultArguments
[idx
];
700 conwriteln("required argument #", idx
+1, " for command '", name
, "' is missing");
705 if (hasArgs(cmdline
)) {
706 conwriteln("too many args for command '", name
, "'");
709 //static if (is(ReturnType!dg == void))
715 static if (is(typeof(&fn
))) {
716 if (aname
.length
== 0) aname
= (&fn
).stringof
[2..$]; // HACK
718 if (aname
.length
> 0) cmdlist
[aname
] = new ConFunc(aname
, ahelp
);
722 // ////////////////////////////////////////////////////////////////////////// //
723 __gshared ConCommand
[string
] cmdlist
;
726 // ////////////////////////////////////////////////////////////////////////// //
727 public void conExec (const(char)[] s
) {
730 auto w
= ConCommand
.getWord(s
);
731 if (w
is null) return;
732 if (auto cmd
= w
in cmdlist
) {
733 while (s
.length
&& s
.ptr
[0] <= 32) s
= s
[1..$];
734 //conwriteln("'", s, "'");
737 auto wrt
= ConWriter
;
739 ConCommand
.writeQuotedString(w
);
744 } catch (Exception
) {
745 conwriteln("error executing console command:\n ", s
);
750 // ////////////////////////////////////////////////////////////////////////// //
751 version(contest_func
) unittest {
752 static void xfunc (int v
, int x
=42) { conwriteln("xfunc: v=", v
, "; x=", x
); }
754 //pragma(msg, typeof(&xfunc), " ", ParameterDefaultValueTuple!xfunc);
755 conRegFunc
!xfunc("", "function with two int args (last has default value '42')");
757 conExec("xfunc 666");
760 conRegFunc
!({conwriteln("!!!");})("bang");
765 // ////////////////////////////////////////////////////////////////////////// //
766 // return `null` when there is no command
767 public const(char)[] conGetCommand (ref const(char)[] s
) {
769 while (s
.length
> 0 && s
[0] <= 32) s
= s
[1..$];
770 if (s
.length
== 0) return null;
771 if (s
.ptr
[0] != ';') break;
778 char qch
= s
.ptr
[pos
++];
779 while (pos
< s
.length
) {
780 if (s
.ptr
[pos
] == qch
) { ++pos
; break; }
781 if (s
.ptr
[pos
++] == '\\') {
782 if (pos
< s
.length
) {
783 if (s
.ptr
[pos
] == 'x' || s
.ptr
[pos
] == 'X') pos
+= 2; else ++pos
;
790 while (pos
< s
.length
) {
791 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
793 } else if (s
.ptr
[pos
++] == '\n') {
799 if (s
.ptr
[0] == '#') {
801 if (pos
>= s
.length
) { s
= s
[$..$]; return null; }
806 while (pos
< s
.length
) {
807 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
809 } else if (s
.ptr
[pos
] == ';' || s
.ptr
[pos
] == '#' || s
.ptr
[pos
] == '\n') {
810 auto res
= s
[0..pos
];
811 if (s
.ptr
[pos
] == '#') s
= s
[pos
..$]; else s
= s
[pos
+1..$];
822 version(contest_cpx
) unittest {
823 const(char)[] s
= "boo; woo \";\" 42#cmt\ntest\nfoo";
825 auto c
= conGetCommand(s
);
826 conwriteln("[", c
, "] : [", s
, "]");
829 auto c
= conGetCommand(s
);
830 conwriteln("[", c
, "] : [", s
, "]");
833 auto c
= conGetCommand(s
);
834 conwriteln("[", c
, "] : [", s
, "]");
837 auto c
= conGetCommand(s
);
838 conwriteln("[", c
, "] : [", s
, "]");
843 // ////////////////////////////////////////////////////////////////////////// //
844 public class ConCommandEcho
: ConCommand
{
845 this () { super("echo", "write string to console"); }
847 override void exec (const(char)[] cmdline
) {
848 if (checkHelp(cmdline
)) { showHelp
; return; }
849 if (!hasArgs(cmdline
)) return;
850 bool needSpace
= false;
851 auto wrt
= ConWriter
;
853 auto w
= getWord(cmdline
);
854 if (w
is null) break;
855 if (needSpace
) wrt(" "); else needSpace
= true;
858 while (pos
< w
.length
&& w
.ptr
[pos
] != '$') ++pos
;
859 if (w
.length
-pos
> 1 && w
.ptr
[pos
+1] == '$') {
862 } else if (w
.length
-pos
<= 1) {
868 if (pos
> 0) wrt(w
[0..pos
]);
870 if (w
.ptr
[pos
] == '{') {
873 while (pos
< w
.length
&& w
.ptr
[pos
] != '}') ++pos
;
875 if (pos
< w
.length
) ++pos
;
880 while (pos
< w
.length
) {
881 char ch
= w
.ptr
[pos
];
882 if (ch
== '_' ||
(ch
>= '0' && ch
<= '9') ||
(ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z')) {
892 if (auto cc
= vname
in cmdlist
) {
893 if (auto cv
= cast(ConVarBase
)(*cc
)) {
894 cv
.printValue(false);
915 shared static this () {
916 cmdlist
["echo"] = new ConCommandEcho();
920 version(contest_echo
) unittest {
921 __gshared
int vi
= 42;
922 __gshared string vs
= "str";
923 __gshared
bool vb
= true;
927 conwriteln("=================");
929 conExec("echo vs=$vs, vi=${vi}, vb=${vb}!");