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;
556 // ////////////////////////////////////////////////////////////////////////// //
557 class ConVar(T
) : ConVarBase
{
559 static if (isIntegral
!T
) {
563 static if (!is(T
: const(char)[])) {
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) {
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") {
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
;
597 override bool isString () const pure nothrow @nogc {
598 static if (is(T
: const(char)[])) {
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)[])) {
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
));
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)[])) {
630 writeQuotedString(*vptr
);
633 } else static if (is(T
== bool)) {
634 conwriteln(name
, " ", (*vptr ?
"tan" : "ona"));
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");
659 conwriteln("vi=", vi
);
660 conwriteln("vs=[", vs
, "]");
670 public void conRegVar(alias fn
, T
) (T aminv
, T amaxv
, string aname
, string ahelp
=null) if (isIntegral
!(typeof(fn
)) && isIntegral
!T
) {
671 if (aname
.length
== 0) aname
= (&fn
).stringof
[2..$]; // HACK
672 if (aname
.length
> 0) cmdlist
[aname
] = new ConVar
!(typeof(fn
))(&fn
, cast(typeof(fn
))aminv
, cast(typeof(fn
))amaxv
, aname
, ahelp
);
675 public void conRegVar(alias fn
) (string aname
, string ahelp
=null) if (!isCallable
!(typeof(fn
))) {
676 if (aname
.length
== 0) aname
= (&fn
).stringof
[2..$]; // HACK
677 if (aname
.length
> 0) cmdlist
[aname
] = new ConVar
!(typeof(fn
))(&fn
, aname
, ahelp
);
681 // ////////////////////////////////////////////////////////////////////////// //
683 public class ConFuncBase
: ConCommand
{
684 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); }
688 public struct ConFuncVA
{
689 const(char)[] cmdline
;
692 // we have to make the class nested, so we can use `dg`, which keeps default args
693 public void conRegFunc(alias fn
) (string aname
, string ahelp
=null) if (isCallable
!fn
) {
694 // hack for inline lambdas
695 static if (is(typeof(&fn
))) {
701 class ConFunc
: ConFuncBase
{
702 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); }
704 override void exec (const(char)[] cmdline
) {
705 if (checkHelp(cmdline
)) { showHelp
; return; }
707 static if (args
.length
== 0) {
708 if (hasArgs(cmdline
)) {
709 conwriteln("too many args for command '", name
, "'");
713 } else static if (args
.length
== 1 && is(typeof(args
[0]) == ConFuncVA
)) {
714 args
[0].cmdline
= cmdline
;
717 alias defaultArguments
= ParameterDefaultValueTuple
!fn
;
718 //pragma(msg, "defs: ", defaultArguments);
719 import std
.conv
: to
;
720 foreach (auto idx
, ref arg
; args
) {
721 // populate arguments, with user data if available,
722 // default if not, and throw if no argument provided
723 if (hasArgs(cmdline
)) {
724 import std
.conv
: ConvException
;
726 arg
= parseType
!(typeof(arg
))(cmdline
);
727 } catch (ConvException
) {
728 conwriteln("error parsing argument #", idx
+1, " for command '", name
, "'");
732 static if (!is(defaultArguments
[idx
] == void)) {
733 arg
= defaultArguments
[idx
];
735 conwriteln("required argument #", idx
+1, " for command '", name
, "' is missing");
740 if (hasArgs(cmdline
)) {
741 conwriteln("too many args for command '", name
, "'");
744 //static if (is(ReturnType!dg == void))
750 static if (is(typeof(&fn
))) {
751 if (aname
.length
== 0) aname
= (&fn
).stringof
[2..$]; // HACK
753 if (aname
.length
> 0) cmdlist
[aname
] = new ConFunc(aname
, ahelp
);
757 // ////////////////////////////////////////////////////////////////////////// //
758 __gshared ConCommand
[string
] cmdlist
;
761 // ////////////////////////////////////////////////////////////////////////// //
762 public void conExecute (const(char)[] s
) {
765 auto w
= ConCommand
.getWord(s
);
766 if (w
is null) return;
767 if (auto cmd
= w
in cmdlist
) {
768 while (s
.length
&& s
.ptr
[0] <= 32) s
= s
[1..$];
769 //conwriteln("'", s, "'");
772 auto wrt
= ConWriter
;
774 ConCommand
.writeQuotedString(w
);
779 } catch (Exception
) {
780 conwriteln("error executing console command:\n ", s
);
785 // ////////////////////////////////////////////////////////////////////////// //
786 version(contest_func
) unittest {
787 static void xfunc (int v
, int x
=42) { conwriteln("xfunc: v=", v
, "; x=", x
); }
789 //pragma(msg, typeof(&xfunc), " ", ParameterDefaultValueTuple!xfunc);
790 conRegFunc
!xfunc("", "function with two int args (last has default value '42')");
791 conExecute("xfunc ?");
792 conExecute("xfunc 666");
795 conRegFunc
!({conwriteln("!!!");})("bang");
798 conRegFunc
!((ConFuncVA va
) {
801 auto w
= ConCommand
.getWord(va
.cmdline
);
802 if (w
is null) break;
803 conwriteln("#", idx
, ": [", w
, "]");
807 conExecute("doo 1 2 ' 34 '");
811 // ////////////////////////////////////////////////////////////////////////// //
812 // return `null` when there is no command
813 public const(char)[] conGetCommand (ref const(char)[] s
) {
815 while (s
.length
> 0 && s
[0] <= 32) s
= s
[1..$];
816 if (s
.length
== 0) return null;
817 if (s
.ptr
[0] != ';') break;
824 char qch
= s
.ptr
[pos
++];
825 while (pos
< s
.length
) {
826 if (s
.ptr
[pos
] == qch
) { ++pos
; break; }
827 if (s
.ptr
[pos
++] == '\\') {
828 if (pos
< s
.length
) {
829 if (s
.ptr
[pos
] == 'x' || s
.ptr
[pos
] == 'X') pos
+= 2; else ++pos
;
836 while (pos
< s
.length
) {
837 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
839 } else if (s
.ptr
[pos
++] == '\n') {
845 if (s
.ptr
[0] == '#') {
847 if (pos
>= s
.length
) { s
= s
[$..$]; return null; }
852 while (pos
< s
.length
) {
853 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
855 } else if (s
.ptr
[pos
] == ';' || s
.ptr
[pos
] == '#' || s
.ptr
[pos
] == '\n') {
856 auto res
= s
[0..pos
];
857 if (s
.ptr
[pos
] == '#') s
= s
[pos
..$]; else s
= s
[pos
+1..$];
868 version(contest_cpx
) unittest {
869 const(char)[] s
= "boo; woo \";\" 42#cmt\ntest\nfoo";
871 auto c
= conGetCommand(s
);
872 conwriteln("[", c
, "] : [", s
, "]");
875 auto c
= conGetCommand(s
);
876 conwriteln("[", c
, "] : [", s
, "]");
879 auto c
= conGetCommand(s
);
880 conwriteln("[", c
, "] : [", s
, "]");
883 auto c
= conGetCommand(s
);
884 conwriteln("[", c
, "] : [", s
, "]");
889 // ////////////////////////////////////////////////////////////////////////// //
890 public class ConCommandEcho
: ConCommand
{
891 this () { super("echo", "write string to console"); }
893 override void exec (const(char)[] cmdline
) {
894 if (checkHelp(cmdline
)) { showHelp
; return; }
895 if (!hasArgs(cmdline
)) return;
896 bool needSpace
= false;
897 auto wrt
= ConWriter
;
899 auto w
= getWord(cmdline
);
900 if (w
is null) break;
901 if (needSpace
) wrt(" "); else needSpace
= true;
904 while (pos
< w
.length
&& w
.ptr
[pos
] != '$') ++pos
;
905 if (w
.length
-pos
> 1 && w
.ptr
[pos
+1] == '$') {
908 } else if (w
.length
-pos
<= 1) {
914 if (pos
> 0) wrt(w
[0..pos
]);
916 if (w
.ptr
[pos
] == '{') {
919 while (pos
< w
.length
&& w
.ptr
[pos
] != '}') ++pos
;
921 if (pos
< w
.length
) ++pos
;
926 while (pos
< w
.length
) {
927 char ch
= w
.ptr
[pos
];
928 if (ch
== '_' ||
(ch
>= '0' && ch
<= '9') ||
(ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z')) {
938 if (auto cc
= vname
in cmdlist
) {
939 if (auto cv
= cast(ConVarBase
)(*cc
)) {
962 shared static this () {
963 cmdlist
["echo"] = new ConCommandEcho();
967 public char[] conFormatStr (char[] dest
, const(char)[] s
) {
970 void put (const(char)[] ss
) {
971 if (ss
.length
== 0) return;
972 auto len
= ss
.length
;
973 if (dest
.length
-dpos
< len
) len
= dest
.length
-dpos
;
975 dest
[dpos
..dpos
+len
] = ss
[];
982 while (pos
< s
.length
&& s
.ptr
[pos
] != '$') ++pos
;
983 if (s
.length
-pos
> 1 && s
.ptr
[pos
+1] == '$') {
986 } else if (s
.length
-pos
<= 1) {
992 if (pos
> 0) put(s
[0..pos
]);
994 if (s
.ptr
[pos
] == '{') {
997 while (pos
< s
.length
&& s
.ptr
[pos
] != '}') ++pos
;
999 if (pos
< s
.length
) ++pos
;
1004 while (pos
< s
.length
) {
1005 char ch
= s
.ptr
[pos
];
1006 if (ch
== '_' ||
(ch
>= '0' && ch
<= '9') ||
(ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z')) {
1016 if (auto cc
= vname
in cmdlist
) {
1017 if (auto cv
= cast(ConVarBase
)(*cc
)) {
1033 return dest
[0..dpos
];
1037 version(contest_echo
) unittest {
1038 __gshared
int vi
= 42;
1039 __gshared string vs
= "str";
1040 __gshared
bool vb
= true;
1044 conRegVar
!vb("r_interpolation");
1045 conwriteln("=================");
1046 conExecute("r_interpolation");
1047 conExecute("echo ?");
1048 conExecute("echo vs=$vs, vi=${vi}, vb=${vb}!");
1051 auto s
= buf
.conFormatStr("vs=$vs, vi=${vi}, vb=${vb}!");
1052 conwriteln("[", s
, "]");
1053 foreach (auto kv
; cmdlist
.byKeyValue
) conwriteln(" ", kv
.key
);
1054 assert("r_interpolation" in cmdlist
);
1055 s
= buf
.conFormatStr("Interpolation: $r_interpolation");
1056 conwriteln("[", s
, "]");