1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 /* contains very simple compile-time format writer
17 * understands [+|-]width[.maxlen]
18 * negative width: add spaces to right
19 * + signed width: center
20 * negative maxlen: get right part
22 * 's': use to!string to write argument
23 * note that writer can print strings, bools, integrals and floats without allocation
24 * 'x': write integer as hex
25 * 'X': write integer as HEX
26 * '!': skip all arguments that's left, no width allowed
27 * '%': just a percent sign, no width allowed
28 * '|': print all arguments that's left with simple "%s", no width allowed
29 * '<...>': print all arguments that's left with simple "%s", delimited with "...", no width allowed
30 * options (must immediately follow '%'):
31 * '~': fill with the following char instead of space
32 * second '~': right filling char for 'center'
34 module iv
.cmdcon
.core
/*is aliced*/;
38 //version = iv_cmdcon_debug_wait;
39 //version = iv_cmdcon_honest_ctfe_writer;
42 // ////////////////////////////////////////////////////////////////////////// //
43 /// use this in conGetVar, for example, to avoid allocations
44 public alias ConString
= const(char)[];
47 // ////////////////////////////////////////////////////////////////////////// //
48 public enum ConDump
: int { none
, stdout
, stderr
}
49 shared ConDump conStdoutFlag
= ConDump
.stdout
;
50 public @property ConDump
conDump () nothrow @trusted @nogc { pragma(inline
, true); import core
.atomic
: atomicLoad
; return atomicLoad(conStdoutFlag
); } /// write console output to ...
51 public @property void conDump (ConDump v
) nothrow @trusted @nogc { pragma(inline
, true); import core
.atomic
: atomicStore
; atomicStore(conStdoutFlag
, v
); } /// ditto
53 shared static this () {
54 conRegVar
!conStdoutFlag("console_dump", "dump console output (none, stdout, stderr)");
58 // ////////////////////////////////////////////////////////////////////////// //
59 import core
.sync
.mutex
: Mutex
;
60 __gshared Mutex consoleLocker
;
61 __gshared Mutex consoleWriteLocker
;
62 shared static this () {
63 consoleLocker
= new Mutex();
64 consoleWriteLocker
= new Mutex();
68 // ////////////////////////////////////////////////////////////////////////// //
69 enum isGShared(alias v
) = !__traits(compiles
, ()@safe{auto _
=&v
;}());
70 enum isShared(alias v
) = is(typeof(v
) == shared);
71 enum isGoodVar(alias v
) = isGShared
!v || isShared
!v
;
72 //alias isGoodVar = isGShared;
75 // ////////////////////////////////////////////////////////////////////////// //
77 private bool strEquCI (const(char)[] s0
, const(char)[] s1
) pure nothrow @trusted @nogc {
78 if (s0
.length
!= s1
.length
) return false;
79 foreach (immutable idx
, char c0
; s0
) {
80 // try the easiest case first
82 if (c0
== s1
[idx
]) continue;
84 if (c0
== s1
.ptr
[idx
]) continue;
86 c0 |
= 0x20; // convert to ascii lowercase
87 if (c0
< 'a' || c0
> 'z') return false; // it wasn't a letter, no need to check the second char
88 // c0 is guaranteed to be a lowercase ascii here
90 if (c0
!= (s1
[idx
]|
0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
92 if (c0
!= (s1
.ptr
[idx
]|
0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
99 // ////////////////////////////////////////////////////////////////////////// //
102 enum ConBufSize = 64;
104 enum ConBufSize = 256*1024;
107 // each line in buffer ends with '\n'; we don't keep offsets or lengthes, as
108 // it's fairly easy to search in buffer, and drawing console is not a common
109 // thing, so it doesn't have to be superfast.
110 __gshared
char* cbuf
= null;
111 __gshared
int cbufhead
, cbuftail
; // `cbuftail` points *at* last char
112 __gshared
bool cbufLastWasCR
= false;
113 __gshared
uint cbufcursize
= 0;
114 __gshared
uint cbufmaxsize
= 256*1024;
116 shared static this () {
117 import core
.stdc
.stdlib
: malloc
;
118 //cbuf.ptr[0] = '\n';
125 cbuf
= cast(char*)malloc(cbufcursize
);
126 if (cbuf
is null) assert(0, "cmdcon: out of memory!"); // unlikely case
127 cbuf
[0..cbufcursize
] = 0;
130 conRegVar("con_bufsize", "current size of console output buffer", (ConVarBase self
) => cbufcursize
, (ConVarBase self
, uint nv
) {}, ConVarAttr
.ReadOnly
);
131 conRegVar
!cbufmaxsize(8192, 512*1024*1024, "con_bufmaxsize", "maximum size of console output buffer");
134 shared uint changeCount
= 1;
135 public @property uint cbufLastChange () nothrow @trusted @nogc { import core
.atomic
; return atomicLoad(changeCount
); } /// changed when something was written to console buffer
138 // ////////////////////////////////////////////////////////////////////////// //
139 version(aliced
) {} else {
140 private auto assumeNoThrowNoGC(T
) (scope T t
) /*if (isFunctionPointer!T || isDelegate!T)*/ {
142 enum attrs
= functionAttributes
!T|FunctionAttribute
.nogc|FunctionAttribute
.nothrow_
;
143 return cast(SetFunctionAttributes
!(T
, functionLinkage
!T
, attrs
)) t
;
149 public void consoleLock() () nothrow @trusted @nogc {
150 version(aliced
) pragma(inline
, true);
152 consoleLocker
.lock();
154 //try { consoleLocker.lock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
155 assumeNoThrowNoGC(() { consoleLocker
.lock(); })();
159 /// multithread unlock
160 public void consoleUnlock() () nothrow @trusted @nogc {
161 version(aliced
) pragma(inline
, true);
163 consoleLocker
.unlock();
165 //try { consoleLocker.unlock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
166 assumeNoThrowNoGC(() { consoleLocker
.unlock(); })();
171 public void consoleWriteLock() () nothrow @trusted @nogc {
172 version(aliced
) pragma(inline
, true);
174 consoleWriteLocker
.lock();
176 //try { consoleWriteLocker.lock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
177 assumeNoThrowNoGC(() { consoleWriteLocker
.lock(); })();
181 // multithread unlock
182 public void consoleWriteUnlock() () nothrow @trusted @nogc {
183 version(aliced
) pragma(inline
, true);
185 consoleWriteLocker
.unlock();
187 //try { consoleWriteLocker.unlock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
188 assumeNoThrowNoGC(() { consoleWriteLocker
.unlock(); })();
192 public __gshared
void delegate () nothrow @trusted @nogc conWasNewLineCB
; /// can be called in any thread; called if some '\n' was output with `conwrite*()`
195 // ////////////////////////////////////////////////////////////////////////// //
196 /// put characters to console buffer (and, possibly, STDOUT_FILENO). thread-safe.
197 public void cbufPut() (scope ConString chrs
...) nothrow @trusted /*@nogc*/ { pragma(inline
, true); if (chrs
.length
) cbufPutIntr
!true(chrs
); }
199 public void cbufPutIntr(bool dolock
) (scope ConString chrs
...) nothrow @trusted /*@nogc*/ {
201 import core
.atomic
: atomicLoad
, atomicOp
;
202 static if (dolock
) consoleWriteLock();
203 static if (dolock
) scope(exit
) consoleWriteUnlock();
204 final switch (/*atomicLoad(conStdoutFlag)*/cast(ConDump
)conStdoutFlag
) {
205 case ConDump
.none
: break;
209 import core
.sys
.windows
.winbase
;
210 import core
.sys
.windows
.windef
;
211 import core
.sys
.windows
.wincon
;
212 auto hh
= GetStdHandle(STD_OUTPUT_HANDLE
);
213 if (hh
!= INVALID_HANDLE_VALUE
) {
216 while (xpos
< chrs
.length
) {
218 while (epos
< chrs
.length
&& chrs
.ptr
[epos
] != '\n') ++epos
;
219 if (epos
> xpos
) WriteConsoleA(hh
, chrs
.ptr
, cast(DWORD
)chrs
.length
, &ww
, null);
220 if (epos
< chrs
.length
) WriteConsoleA(hh
, "\r\n".ptr
, cast(DWORD
)2, &ww
, null);
225 import core
.sys
.posix
.unistd
: STDOUT_FILENO
, write
;
226 write(STDOUT_FILENO
, chrs
.ptr
, chrs
.length
);
232 import core
.sys
.windows
.winbase
;
233 import core
.sys
.windows
.windef
;
234 import core
.sys
.windows
.wincon
;
235 auto hh
= GetStdHandle(STD_ERROR_HANDLE
);
236 if (hh
!= INVALID_HANDLE_VALUE
) {
239 while (xpos
< chrs
.length
) {
241 while (epos
< chrs
.length
&& chrs
.ptr
[epos
] != '\n') ++epos
;
242 if (epos
> xpos
) WriteConsoleA(hh
, chrs
.ptr
, cast(DWORD
)chrs
.length
, &ww
, null);
243 if (epos
< chrs
.length
) WriteConsoleA(hh
, "\r\n".ptr
, cast(DWORD
)2, &ww
, null);
248 import core
.sys
.posix
.unistd
: STDERR_FILENO
, write
;
249 write(STDERR_FILENO
, chrs
.ptr
, chrs
.length
);
253 //atomicOp!"+="(changeCount, 1);
254 bool wasNewLine
= false;
255 (*cast(uint*)&changeCount
)++;
256 foreach (char ch
; chrs
) {
257 if (cbufLastWasCR
&& ch
== '\x0a') { cbufLastWasCR
= false; continue; }
258 if ((cbufLastWasCR
= (ch
== '\x0d')) != false) ch
= '\x0a';
259 wasNewLine
= wasNewLine ||
(ch
== '\x0a');
260 int np
= (cbuftail
+1)%cbufcursize
;
261 if (np
== cbufhead
) {
262 // we have to make some room; delete top line for this
263 bool removelines
= true;
264 // first, try grow cosole buffer if we can
265 if (cbufcursize
< cbufmaxsize
) {
266 import core
.stdc
.stdlib
: realloc
;
268 auto newsz
= cbufcursize
+13;
270 auto newsz
= cbufcursize
*2;
272 if (newsz
> cbufmaxsize
) newsz
= cbufmaxsize
;
273 //{ import core.stdc.stdio : stderr, fprintf; stderr.fprintf("*** CON: %u -> %u; head=%u; tail=%u; np=%u\n", cbufcursize, newsz, cbufhead, cbuftail, np); }
274 auto nbuf
= cast(char*)realloc(cbuf
, newsz
);
276 // yay, we got some room; move text down, so there will be more room
277 version(test_cbuf
) { import core
.stdc
.stdio
: stderr
, fprintf
; stderr
.fprintf("np=%u; cbufcursize=%u; cbufmaxsize=%u; newsz=%u; ndest=%u\n", np
, cbufcursize
, cbufmaxsize
, newsz
, newsz
-cbufcursize
, cbuf
+np
); }
279 // yay, we can do some trickery here
280 np
= cbuftail
+1; // yep, that easy
282 import core
.stdc
.string
: memmove
;
283 auto tsz
= cbufcursize
-np
;
284 if (tsz
> 0) memmove(cbuf
+newsz
-tsz
, cbuf
+np
, tsz
);
285 cbufhead
+= newsz
-cbufcursize
;
290 version(test_cbuf
) { import core
.stdc
.stdio
: stderr
, fprintf
; stderr
.fprintf(" cbufhead=%u; cbuftail=%u\n", cbufhead
, cbuftail
); }
291 assert(np
< cbufcursize
);
296 char och
= cbuf
[cbufhead
];
297 cbufhead
= (cbufhead
+1)%cbufcursize
;
298 if (cbufhead
== np || och
== '\n') break;
305 if (wasNewLine
&& conWasNewLineCB
!is null) conWasNewLineCB();
310 // ////////////////////////////////////////////////////////////////////////// //
311 /// range of conbuffer lines, from last. not thread-safe.
312 /// warning! don't modify conbuf while the range is active!
313 public auto conbufLinesRev () nothrow @trusted @nogc {
315 nothrow @trusted @nogc:
317 int h
, t
; // head and tail, to check validity
321 @property auto front () const { pragma(inline
, true); return (sp
>= 0 ? cbuf
[sp
] : '\x00'); }
322 @property bool empty () const { pragma(inline
, true); return (sp
< 0 || h
!= cbufhead || t
!= cbuftail
); }
323 @property auto save () { pragma(inline
, true); return Line(h
, t
, sp
, ep
); }
324 void popFront () { pragma(inline
, true); if (sp
< 0 ||
(sp
= (sp
+1)%cbufcursize
) == ep
) sp
= -1; }
325 @property usize
opDollar () { pragma(inline
, true); return (sp
>= 0 ?
(sp
> ep ? ep
+cbufcursize
-sp
: ep
-sp
) : 0); }
326 alias length
= opDollar
;
327 char opIndex (usize pos
) { pragma(inline
, true); return (sp
>= 0 ? cbuf
[sp
+pos
] : '\x00'); }
330 static struct Range
{
331 nothrow @trusted @nogc:
333 int h
, t
; // head and tail, to check validity
334 int pos
; // position of prev line
337 void toLineStart () {
339 while (pos
!= cbufhead
) {
340 int p
= (pos
+cbufcursize
-1)%cbufcursize
;
341 if (cbuf
[p
] == '\n') break;
350 @property auto front () pure { pragma(inline
, true); return line
; }
351 @property bool empty () const { pragma(inline
, true); return (pos
< 0 || pos
== h || h
!= cbufhead || t
!= cbuftail
); }
352 @property auto save () { pragma(inline
, true); return Range(h
, t
, pos
, line
); }
354 if (pos
< 0 || pos
== h || h
!= cbufhead || t
!= cbuftail
) { line
= Line
.init
; h
= t
= pos
= -1; return; }
355 pos
= (pos
+cbufcursize
-1)%cbufcursize
;
362 res
.pos
= res
.t
= cbuftail
;
363 if (cbuf
[res
.pos
] != '\n') res
.pos
= (res
.pos
+1)%cbufcursize
;
365 //{ import std.stdio; writeln("pos=", res.pos, "; head=", res.h, "; tail=", res.t, "; llen=", res.line.length, "; [", res.line, "]"); }
370 // ////////////////////////////////////////////////////////////////////////// //
371 version(unittest) public void conbufDump () {
374 stdout
.writeln("==========================");
376 if (cbuf
[pp
] == '\n') stdout
.write('|');
377 stdout
.write(cbuf
[pp
]);
378 if (pp
== cbuftail
) {
379 if (cbuf
[pp
] != '\n') stdout
.write('\n');
382 pp
= (pp
+1)%cbufcursize
;
384 //foreach (/+auto+/ s; conbufLinesRev) stdout.writeln(s, "|");
388 // ////////////////////////////////////////////////////////////////////////// //
389 version(test_cbuf
) unittest {
391 cbufPut("boo\n"); conbufDump();
392 cbufPut("this is another line\n"); conbufDump();
393 cbufPut("one more line\n"); conbufDump();
394 cbufPut("foo\n"); conbufDump();
395 cbufPut("more lines!\n"); conbufDump();
396 cbufPut("and even more lines!\n"); conbufDump();
397 foreach (immutable idx
; 0..256) {
398 import std
.string
: format
;
399 cbufPut("line %s\n".format(idx
));
405 // ////////////////////////////////////////////////////////////////////////// //
407 private import std
.traits
/* : isBoolean, isIntegral, isPointer*/;
408 //private alias StripTypedef(T) = T;
410 //__gshared void delegate (scope const(char[])) @trusted nothrow @nogc conwriter;
411 //public @property auto ConWriter () @trusted nothrow @nogc { return conwriter; }
412 //public @property auto ConWriter (typeof(conwriter) cv) @trusted nothrow @nogc { auto res = conwriter; conwriter = cv; return res; }
414 void conwriter() (scope ConString
str) nothrow @trusted /*@nogc*/ {
415 pragma(inline
, true);
416 if (str.length
> 0) cbufPut(str);
420 // ////////////////////////////////////////////////////////////////////////// //
421 // i am VERY sorry for this!
422 private template XUQQ(T
) {
423 static if (is(T
: shared TT
, TT
)) {
424 static if (is(TT
: const TX
, TX
)) alias XUQQ
= TX
;
425 else static if (is(TT
: immutable TX
, TX
)) alias XUQQ
= TX
;
426 else alias XUQQ
= TT
;
427 } else static if (is(T
: const TT
, TT
)) {
428 static if (is(TT
: shared TX
, TX
)) alias XUQQ
= TX
;
429 else static if (is(TT
: immutable TX
, TX
)) alias XUQQ
= TX
;
430 else alias XUQQ
= TT
;
431 } else static if (is(T
: immutable TT
, TT
)) {
432 static if (is(TT
: const TX
, TX
)) alias XUQQ
= TX
;
433 else static if (is(TT
: shared TX
, TX
)) alias XUQQ
= TX
;
434 else alias XUQQ
= TT
;
439 static assert(is(XUQQ
!string
== string
));
440 static assert(is(XUQQ
!(const(char)[]) == const(char)[]));
443 // ////////////////////////////////////////////////////////////////////////// //
444 private void cwrxputch (scope const(char)[] s
...) nothrow @trusted @nogc {
445 pragma(inline
, true);
446 if (s
.length
) cbufPutIntr
!false(s
); // no locking
450 private void cwrxputstr(bool cutsign
=false) (scope const(char)[] str, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
452 if (maxwdt
== maxwdt
.min
) ++maxwdt
; // alas
454 if (str.length
> maxwdt
) str = str[$-maxwdt
..$];
455 } else if (maxwdt
> 0) {
456 if (str.length
> maxwdt
) str = str[0..maxwdt
];
458 static if (cutsign
) {
459 if (signw
== ' ' && wdt
&& str.length
&& str.length
< wdt
) {
460 if (str.ptr
[0] == '-' ||
str.ptr
[0] == '+') {
461 cwrxputch(str.ptr
[0]);
467 if (str.length
< wdt
) {
468 // '+' means "center"
470 foreach (immutable _
; 0..(wdt
-str.length
)/2) cwrxputch(lchar
);
471 } else if (signw
!= '-') {
472 foreach (immutable _
; 0..wdt
-str.length
) cwrxputch(lchar
);
476 if (str.length
< wdt
) {
477 // '+' means "center"
479 foreach (immutable _
; ((wdt
-str.length
)/2)+str.length
..wdt
) cwrxputch(rchar
);
480 } else if (signw
== '-') {
481 foreach (immutable _
; 0..wdt
-str.length
) cwrxputch(rchar
);
487 private void cwrxputstrz(bool cutsign
=false) (scope const(char)* str, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
489 cwrxputstr
!cutsign("", signw
, lchar
, rchar
, wdt
, maxwdt
);
493 cwrxputstr
!cutsign(str[0..cast(usize
)(end
-str)], signw
, lchar
, rchar
, wdt
, maxwdt
);
498 private void cwrxputchar (char ch
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
499 cwrxputstr((&ch
)[0..1], signw
, lchar
, rchar
, wdt
, maxwdt
);
503 private void cwrxputint(TT
) (TT nn
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
506 static if (is(TT
== shared)) {
509 T n
= atomicLoad(nn
);
515 static if (is(T
== long)) {
516 if (n
== 0x8000_0000_0000_0000uL) { cwrxputstr
!true("-9223372036854775808", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
517 } else static if (is(T
== int)) {
518 if (n
== 0x8000_0000u) { cwrxputstr
!true("-2147483648", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
519 } else static if (is(T
== short)) {
520 if ((n
&0xffff) == 0x8000u
) { cwrxputstr
!true("-32768", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
521 } else static if (is(T
== byte)) {
522 if ((n
&0xff) == 0x80u
) { cwrxputstr
!true("-128", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
525 static if (__traits(isUnsigned
, T
)) {
532 int bpos
= buf
.length
;
534 //if (bpos == 0) assert(0, "internal printer error");
535 buf
.ptr
[--bpos
] = cast(char)('0'+n
%10);
539 //if (bpos == 0) assert(0, "internal printer error");
540 buf
.ptr
[--bpos
] = '-';
542 cwrxputstr
!true(buf
[bpos
..$], signw
, lchar
, rchar
, wdt
, maxwdt
);
546 private void cwrxputhex(TT
) (TT nn
, bool upcase
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
549 static if (is(TT
== shared)) {
552 T n
= atomicLoad(nn
);
558 static if (is(T
== long)) {
559 if (n
== 0x8000_0000_0000_0000uL) { cwrxputstr
!true("-8000000000000000", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
560 } else static if (is(T
== int)) {
561 if (n
== 0x8000_0000u) { cwrxputstr
!true("-80000000", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
562 } else static if (is(T
== short)) {
563 if (n
== 0x8000u
) { cwrxputstr
!true("-8000", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
564 } else static if (is(T
== byte)) {
565 if (n
== 0x80u
) { cwrxputstr
!true("-80", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
568 static if (__traits(isUnsigned
, T
)) {
575 int bpos
= buf
.length
;
577 //if (bpos == 0) assert(0, "internal printer error");
578 immutable ubyte b
= n
&0x0f;
580 if (b
< 10) buf
.ptr
[--bpos
] = cast(char)('0'+b
); else buf
.ptr
[--bpos
] = cast(char)((upcase ?
'A' : 'a')+(b
-10));
583 //if (bpos == 0) assert(0, "internal printer error");
584 buf
.ptr
[--bpos
] = '-';
586 cwrxputstr
!true(buf
[bpos
..$], signw
, lchar
, rchar
, wdt
, maxwdt
);
590 private void cwrxputfloat(TT
) (TT nn
, bool simple
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
591 import core
.stdc
.stdlib
: malloc
, realloc
;
593 static if (is(TT
== shared)) {
596 T n
= atomicLoad(nn
);
603 static usize buflen
= 0;
609 buf
= cast(char*)malloc(buflen
);
610 if (buf
is null) assert(0, "out of memory");
613 void putNum (int n
) {
614 if (n
== n
.min
) assert(0, "oops");
615 if (n
< 0) { fmtstr
.ptr
[fspos
++] = '-'; n
= -(n
); }
617 int bpos
= buf
.length
;
619 buf
.ptr
[--bpos
] = cast(char)('0'+n
%10);
622 if (fmtstr
.length
-fspos
< buf
.length
-bpos
) assert(0, "internal printer error");
623 fmtstr
.ptr
[fspos
..fspos
+(buf
.length
-bpos
)] = buf
[bpos
..$];
624 fspos
+= buf
.length
-bpos
;
627 fmtstr
.ptr
[fspos
++] = '%';
630 if (signw
== '-') fmtstr
.ptr
[fspos
++] = '-';
634 fmtstr
.ptr
[fspos
++] = '.';
637 fmtstr
.ptr
[fspos
++] = 'g';
640 fmtstr
.ptr
[fspos
++] = 'g';
642 fmtstr
.ptr
[fspos
++] = '\x00';
643 //{ import core.stdc.stdio; printf("<%s>", fmtstr.ptr); }
646 import core
.stdc
.stdio
: snprintf
;
647 auto plen
= snprintf(buf
, buflen
, fmtstr
.ptr
, cast(double)n
);
648 if (plen
>= buflen
) {
650 buf
= cast(char*)realloc(buf
, buflen
);
651 if (buf
is null) assert(0, "out of memory");
654 foreach (ref ch
; buf
[0..plen
]) {
655 if (ch
!= ' ') break;
660 foreach_reverse (ref ch
; buf
[0..plen
]) {
661 if (ch
!= ' ') break;
665 //{ import core.stdc.stdio; printf("<{%s}>", buf); }
666 cwrxputstr
!true(buf
[0..plen
], signw
, lchar
, rchar
, wdt
, maxwdt
);
673 private void cwrxputenum(TT
) (TT nn
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
674 static if (is(TT
== shared)) {
677 T n
= atomicLoad(nn
);
683 foreach (string mname
; __traits(allMembers
, T
)) {
684 if (n
== __traits(getMember
, T
, mname
)) {
685 cwrxputstr
!false(mname
, signw
, lchar
, rchar
, wdt
, maxwdt
);
689 static if (__traits(isUnsigned
, T
)) {
690 cwrxputint
!long(cast(long)n
, signw
, lchar
, rchar
, wdt
, maxwdt
);
692 cwrxputint
!ulong(cast(ulong)n
, signw
, lchar
, rchar
, wdt
, maxwdt
);
697 // ////////////////////////////////////////////////////////////////////////// //
698 /** write formatted string to console with runtime format string
699 * will try to not allocate if it is possible.
701 * understands [+|-]width[.maxlen]
702 * negative width: add spaces to right
703 * + signed width: center
704 * negative maxlen: get right part
706 * 's': use to!string to write argument
707 * note that writer can print strings, bools, integrals and floats without allocation
708 * 'x': write integer as hex
709 * 'X': write integer as HEX
710 * '!': skip all arguments that's left, no width allowed
711 * '%': just a percent sign, no width allowed
712 * '|': print all arguments that's left with simple "%s", no width allowed
713 * '<...>': print all arguments that's left with simple "%s", delimited with "...", no width allowed
714 * options (must immediately follow '%'): NOT YET
715 * '~': fill with the following char instead of space
716 * second '~': right filling char for 'center'
718 public void conprintfX(bool donl
, A
...) (ConString fmt
, in auto ref A args
) {
719 import core
.stdc
.stdio
: snprintf
;
720 char[128] tmp
= void;
724 void put (const(char)[] s
...) nothrow @trusted @nogc {
725 foreach (char ch
; s
) {
726 if (tlen
>= tmp
.length
) { tlen
= tmp
.length
+42; break; }
727 tmp
.ptr
[tlen
++] = ch
;
732 static struct IntNum
{ char sign
; int aval
; bool leadingZero
; /*|value|*/ }
734 static char[] utf8Encode (char[] s
, dchar c
) pure nothrow @trusted @nogc {
735 assert(s
.length
>= 4);
736 if (c
> 0x10FFFF) c
= '\uFFFD';
738 s
.ptr
[0] = cast(char)c
;
742 s
.ptr
[0] = cast(char)(0xC0|
(c
>>6));
743 s
.ptr
[1] = cast(char)(0x80|
(c
&0x3F));
745 } else if (c
<= 0xFFFF) {
746 s
.ptr
[0] = cast(char)(0xE0|
(c
>>12));
747 s
.ptr
[1] = cast(char)(0x80|
((c
>>6)&0x3F));
748 s
.ptr
[2] = cast(char)(0x80|
(c
&0x3F));
750 } else if (c
<= 0x10FFFF) {
751 s
.ptr
[0] = cast(char)(0xF0|
(c
>>18));
752 s
.ptr
[1] = cast(char)(0x80|
((c
>>12)&0x3F));
753 s
.ptr
[2] = cast(char)(0x80|
((c
>>6)&0x3F));
754 s
.ptr
[3] = cast(char)(0x80|
(c
&0x3F));
763 void parseInt(bool allowsign
) (ref IntNum n
) nothrow @trusted @nogc {
766 n
.leadingZero
= false;
767 if (fmt
.length
== 0) return;
768 static if (allowsign
) {
769 if (fmt
.ptr
[0] == '-' || fmt
.ptr
[0] == '+') {
772 if (fmt
.length
== 0) return;
775 if (fmt
.ptr
[0] < '0' || fmt
.ptr
[0] > '9') return;
776 n
.leadingZero
= (fmt
.ptr
[0] == '0');
777 while (fmt
.length
&& fmt
.ptr
[0] >= '0' && fmt
.ptr
[0] <= '9') {
778 n
.aval
= n
.aval
*10+(fmt
.ptr
[0]-'0'); // ignore overflow here
783 void skipLitPart () nothrow @trusted @nogc {
784 while (fmt
.length
> 0) {
785 import core
.stdc
.string
: memchr
;
786 auto prcptr
= cast(const(char)*)memchr(fmt
.ptr
, '%', fmt
.length
);
787 if (prcptr
is null) prcptr
= fmt
.ptr
+fmt
.length
;
788 // print (and remove) literal part
789 if (prcptr
!is fmt
.ptr
) {
790 auto lplen
= cast(usize
)(prcptr
-fmt
.ptr
);
791 cwrxputch(fmt
[0..lplen
]);
794 if (fmt
.length
>= 2 && fmt
.ptr
[0] == '%' && fmt
.ptr
[1] == '%') {
803 if (fmt
.length
== 0) return;
805 scope(exit
) consoleWriteUnlock();
806 auto fmtanchor
= fmt
;
810 ConString smpDelim
= null;
811 ConString fmtleft
= null;
813 argloop
: foreach (immutable argnum
, /*auto*/ att
; A
) {
815 // skip literal part of the format string
817 // something's left in the format string?
818 if (fmt
.length
> 0) {
819 // here we should have at least two chars
820 assert(fmt
[0] == '%');
821 if (fmt
.length
== 1) { cwrxputch("<stray-percent-in-conprintf>"); break argloop
; }
824 if (fmt
.length
&& fmt
[0] == '.') {
826 parseInt
!false(maxlen
);
830 maxlen
.leadingZero
= false;
832 if (fmt
.length
== 0) { cwrxputch("<stray-percent-in-conprintf>"); break argloop
; }
836 case '!': break argloop
; // no more
837 case '|': // rock 'till the end
838 wdt
.sign
= maxlen
.sign
= ' ';
839 wdt
.aval
= maxlen
.aval
= 0;
840 wdt
.leadingZero
= maxlen
.leadingZero
= false;
841 if (fmt
!is null) fmtleft
= fmt
;
844 case '<': // <...>: print all arguments that's left with simple "%s", delimited with "...", no width allowed
845 wdt
.sign
= maxlen
.sign
= ' ';
846 wdt
.aval
= maxlen
.aval
= 0;
847 wdt
.leadingZero
= maxlen
.leadingZero
= false;
849 while (epos
< fmt
.length
&& fmt
[epos
] != '>') ++epos
;
850 smpDelim
= fmt
[0..epos
];
851 if (epos
< fmt
.length
) ++epos
;
852 fmtleft
= fmt
[epos
..$];
855 case 's': // process as simple string
857 case 'd': case 'D': // decimal, process as simple string
858 case 'u': case 'U': // unsigned decimal, process as simple string
859 static if (is(at
== bool)) {
860 cwrxputint((args
[argnum
] ?
1 : 0), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
862 } else static if (is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar)) {
863 cwrxputint(cast(uint)(args
[argnum
] ?
1 : 0), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
865 } else static if (is(at
== enum)) {
866 static if (at
.min
>= int.min
&& at
.max
<= int.max
) {
867 cwrxputint(cast(int)args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
868 } else static if (at
.min
>= 0 && at
.max
<= uint.max
) {
869 cwrxputint(cast(uint)args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
870 } else static if (at
.min
>= long.min
&& at
.max
<= long.max
) {
871 cwrxputint(cast(long)args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
873 cwrxputint(cast(ulong)args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
879 case 'c': case 'C': // char
880 static if (is(at
== bool)) {
881 cwrxputstr
!false((args
[argnum
] ?
"t" : "f"), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
882 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
883 cwrxputstr
!false(utf8Encode(tmp
[], cast(dchar)args
[argnum
]), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
884 } else static if (is(at
== byte) ||
is(at
== short) ||
is(at
== int) ||
is(at
== long) ||
885 is(at
== ubyte) ||
is(at
== ushort) ||
is(at
== uint) ||
is(at
== ulong) ||
888 tmp
[0] = cast(char)args
[argnum
]; // nobody cares about unifuck
889 cwrxputstr
!false(tmp
[0..1], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
891 cwrxputch("<cannot-charprint-");
892 cwrxputch(at
.stringof
);
893 cwrxputch("in-conprintf>");
896 case 'x': case 'X': // hex
897 static if (is(at
== bool)) {
898 cwrxputint((args
[argnum
] ?
1 : 0), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
899 } else static if (is(at
== byte) ||
is(at
== short) ||
is(at
== int) ||
is(at
== long) ||
900 is(at
== ubyte) ||
is(at
== ushort) ||
is(at
== uint) ||
is(at
== ulong))
902 //cwrxputint(args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
903 cwrxputhex(args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
904 } else static if (is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar)) {
905 cwrxputhex(cast(uint)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
906 } else static if (is(at
== enum)) {
907 static if (at
.min
>= int.min
&& at
.max
<= int.max
) {
908 cwrxputhex(cast(int)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
909 } else static if (at
.min
>= 0 && at
.max
<= uint.max
) {
910 cwrxputhex(cast(uint)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
911 } else static if (at
.min
>= long.min
&& at
.max
<= long.max
) {
912 cwrxputhex(cast(long)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
914 cwrxputhex(cast(ulong)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
916 } else static if (is(at
: T
*, T
)) {
918 cwrxputhex(cast(usize
)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
920 cwrxputch("<cannot-hexprint-");
921 cwrxputch(at
.stringof
);
922 cwrxputch("in-conprintf>");
926 static if (is(at
== bool)) {
927 cwrxputfloat((args
[argnum
] ?
1.0f : 0.0f), false, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
928 } else static if (is(at
== byte) ||
is(at
== short) ||
is(at
== int) ||
is(at
== long) ||
929 is(at
== ubyte) ||
is(at
== ushort) ||
is(at
== uint) ||
is(at
== ulong) ||
930 is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar))
932 cwrxputfloat(cast(double)args
[argnum
], false, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
933 } else static if (is(at
== float) ||
is(at
== double)) {
934 cwrxputfloat(args
[argnum
], false, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
936 cwrxputch("<cannot-floatprint-");
937 cwrxputch(at
.stringof
);
938 cwrxputch("in-conprintf>");
942 cwrxputch("<invalid-format-in-conprintf(%");
943 if (mode
< 32 || mode
== 127) {
944 tlen
= snprintf(tmp
.ptr
, tmp
.length
, "\\x%02x", cast(uint)mode
);
945 cwrxputch(tmp
[0..tlen
]);
953 if (smpDelim
.length
) cwrxputch(smpDelim
);
954 wdt
.sign
= maxlen
.sign
= ' ';
955 wdt
.aval
= maxlen
.aval
= 0;
956 wdt
.leadingZero
= maxlen
.leadingZero
= false;
959 static if (is(at
== bool)) {
960 cwrxputstr
!false((args
[argnum
] ?
"true" : "false"), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
961 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
962 cwrxputstr
!false(utf8Encode(tmp
[], cast(dchar)args
[argnum
]), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
963 } else static if (is(at
== byte) ||
is(at
== short) ||
is(at
== int) ||
is(at
== long) ||
964 is(at
== ubyte) ||
is(at
== ushort) ||
is(at
== uint) ||
is(at
== ulong))
966 cwrxputint(args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
967 } else static if (is(at
== char)) {
968 tmp
[0] = cast(char)args
[argnum
]; // nobody cares about unifuck
969 cwrxputstr
!false(tmp
[0..1], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
970 } else static if (is(at
== float) ||
is(at
== double)) {
971 cwrxputfloat(args
[argnum
], false, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
972 } else static if (is(at
== enum)) {
973 cwrxputenum(args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
974 } else static if (is(at
: T
*, T
)) {
976 static if (is(XUQQ
!T
== char)) {
977 // special case: `char*` will be printed as 0-terminated string
978 cwrxputstrz(args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
980 // other pointers will be printed as "0x..."
981 if (wdt
.aval
== 0) { wdt
.aval
= 8/*cast(int)usize.sizeof*2*/; wdt
.leadingZero
= true; }
982 cwrxputhex(cast(usize
)args
[argnum
], true, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
984 } else static if (is(at
: const(char)[])) {
986 cwrxputstr
!false(args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
990 import std
.format
: formatValue
, singleSpec
;
993 scope spec
= singleSpec("%s");
994 formatValue(wrt
, args
[argnum
], spec
);
995 if (tlen
> tmp
.length
) {
996 cwrxputch("<error-formatting-in-conprintf>");
998 cwrxputstr
!false(tmp
[0..tlen
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
1000 } catch (Exception e
) {
1001 cwrxputch("<error-formatting-in-conprintf>");
1007 if (fmtleft
.length
) cwrxputch(fmtleft
);
1013 assert(fmt
[0] == '%');
1014 if (fmt
.length
== 1) {
1015 cwrxputch("<stray-percent-in-conprintf>");
1019 if (fmt
.length
&& fmt
[0] == '.') {
1021 parseInt
!false(maxlen
);
1023 if (fmt
.length
== 0) {
1024 cwrxputch("<stray-percent-in-conprintf>");
1028 if (mode
== '!' || mode
== '|') {
1029 if (fmt
.length
) cwrxputch(fmt
);
1030 } else if (mode
== '<') {
1031 while (fmt
.length
&& fmt
[0] != '>') fmt
= fmt
[1..$];
1032 if (fmt
.length
) fmt
= fmt
[1..$];
1033 if (fmt
.length
) cwrxputch(fmt
);
1035 cwrxputch("<orphan-format-in-conprintf(%");
1036 if (mode
< 32 || mode
== 127) {
1037 tlen
= snprintf(tmp
.ptr
, tmp
.length
, "\\x%02x", cast(uint)mode
);
1038 cwrxputch(tmp
[0..tlen
]);
1049 static if (donl
) cwrxputch("\n");
1052 public void conprintf(A
...) (ConString fmt
, in auto ref A args
) { conprintfX
!false(fmt
, args
); }
1053 public void conprintfln(A
...) (ConString fmt
, in auto ref A args
) { conprintfX
!true(fmt
, args
); }
1056 // ////////////////////////////////////////////////////////////////////////// //
1057 version(iv_cmdcon_honest_ctfe_writer
)
1058 /// write formatted string to console with compile-time format string
1059 public template conwritef(string fmt
, A
...) {
1060 private string
gen() () {
1064 //string simpledelim;
1066 uint simpledelimpos
;
1068 res
.length
= fmt
.length
+128;
1070 void putRes (const(char)[] s
...) {
1071 //if (respos+s.length > res.length) res.length = respos+s.length+256;
1072 foreach (char ch
; s
) {
1073 if (respos
>= res
.length
) res
.length
= res
.length
*2;
1078 void putSD (const(char)[] s
...) {
1079 if (simpledelim
.length
== 0) simpledelim
.length
= 128;
1080 foreach (char ch
; s
) {
1081 if (simpledelimpos
>= simpledelim
.length
) simpledelim
.length
= simpledelim
.length
*2;
1082 simpledelim
[simpledelimpos
++] = ch
;
1086 void putNum(bool hex
=false) (int n
, int minlen
=-1) {
1087 if (n
== n
.min
) assert(0, "oops");
1088 if (n
< 0) { putRes('-'); n
= -(n
); }
1090 int bpos
= buf
.length
;
1093 buf
[--bpos
] = "0123456789abcdef"[n
&0x0f];
1096 buf
[--bpos
] = cast(char)('0'+n
%10);
1100 } while (n
!= 0 || minlen
> 0);
1101 putRes(buf
[bpos
..$]);
1104 int parseNum (ref char sign
, ref bool leadzero
) {
1107 if (pos
>= fmt
.length
) return 0;
1108 if (fmt
[pos
] == '-' || fmt
[pos
] == '+') sign
= fmt
[pos
++];
1109 if (pos
>= fmt
.length
) return 0;
1111 if (pos
< fmt
.length
&& fmt
[pos
] == '0') leadzero
= true;
1112 while (pos
< fmt
.length
&& fmt
[pos
] >= '0' && fmt
[pos
] <= '9') res
= res
*10+fmt
[pos
++]-'0';
1116 void processUntilFSp () {
1117 while (pos
< fmt
.length
) {
1119 while (epos
< fmt
.length
&& fmt
[epos
] != '%') {
1120 if (fmt
[epos
] < ' ' && fmt
[epos
] != '\t' && fmt
[epos
] != '\n') break;
1121 if (fmt
[epos
] >= 127 || fmt
[epos
] == '`') break;
1125 putRes("cwrxputch(`");
1126 putRes(fmt
[pos
..epos
]);
1129 if (pos
>= fmt
.length
) break;
1131 if (fmt
[pos
] != '%') {
1132 putRes("cwrxputch('\\x");
1133 putNum
!true(cast(ubyte)fmt
[pos
], 2);
1138 if (fmt
.length
-pos
< 2 || fmt
[pos
+1] == '%') { putRes("cwrxputch('%');\n"); pos
+= 2; continue; }
1143 bool simples
= false;
1144 bool putsimpledelim
= false;
1145 char lchar
=' ', rchar
=' ';
1147 bool leadzerow
= false;
1151 argloop
: foreach (immutable argnum
, /*auto*/ att
; A
) {
1152 alias at
= XUQQ
!att
;
1155 if (pos
>= fmt
.length
) assert(0, "out of format specifiers for arguments");
1156 assert(fmt
[pos
] == '%');
1158 if (pos
< fmt
.length
&& fmt
[pos
] == '!') { ++pos
; break; } // skip rest
1159 if (pos
< fmt
.length
&& fmt
[pos
] == '|') {
1162 } else if (pos
< fmt
.length
&& fmt
[pos
] == '<') {
1166 while (ep
< fmt
.length
) {
1167 if (fmt
[ep
] == '>') break;
1168 if (fmt
[ep
] == '\\') ++ep
;
1171 if (ep
>= fmt
.length
) assert(0, "invalid format string");
1174 bool hasQuote
= false;
1175 foreach (char ch
; fmt
[pos
..ep
]) if (ch
== '\\' ||
(ch
< ' ' && ch
!= '\n' && ch
!= '\t') || ch
>= 127 || ch
== '`') { hasQuote
= true; break; }
1177 putSD("cwrxputch(`");
1178 putSD(fmt
[pos
..ep
]);
1181 //FIXME: get rid of char-by-char processing!
1182 putSD("cwrxputch(\"");
1184 char ch
= fmt
[pos
++];
1185 if (ch
== '\\') ch
= fmt
[pos
++];
1186 if (ch
== '\\' || ch
< ' ' || ch
>= 127 || ch
== '"') {
1188 putSD("0123456789abcdef"[ch
>>4]);
1189 putSD("0123456789abcdef"[ch
&0x0f]);
1199 lchar
= rchar
= ' ';
1201 if (pos
< fmt
.length
&& fmt
[pos
] == '~') {
1203 if (fmt
.length
-pos
< 2) assert(0, "invalid format string");
1206 if (pos
< fmt
.length
&& fmt
[pos
] == '~') {
1207 if (fmt
.length
-pos
< 2) assert(0, "invalid format string");
1212 wdt
= parseNum(signw
, leadzerow
);
1213 if (pos
>= fmt
.length
) assert(0, "invalid format string");
1214 if (!lrset
&& leadzerow
) lchar
= '0';
1215 if (fmt
[pos
] == '.') {
1219 maxwdt
= parseNum(mws
, lzw
);
1220 if (mws
== '-') maxwdt
= -maxwdt
;
1224 if (pos
>= fmt
.length
) assert(0, "invalid format string");
1229 lchar
= rchar
= signw
= ' ';
1233 if (putsimpledelim
&& simpledelimpos
> 0) {
1234 putRes(simpledelim
[0..simpledelimpos
]);
1236 putsimpledelim
= true;
1243 static if (is(at
== char)) {
1244 putRes("cwrxputchar(args[");
1247 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
1248 putRes("import std.conv : to; cwrxputstr(to!string(args[");
1251 } else static if (is(at
== bool)) {
1252 putRes("cwrxputstr((args[");
1254 putRes("] ? `true` : `false`)");
1255 } else static if (is(at
== enum)) {
1256 putRes("cwrxputenum(args[");
1259 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
1260 putRes("cwrxputfloat(args[");
1263 } else static if (is(at
: const(char)[])) {
1264 putRes("cwrxputstr(args[");
1267 } else static if (is(at
: T
*, T
)) {
1268 //putRes("cwrxputch(`0x`); ");
1269 static if (is(T
== char)) {
1270 putRes("cwrxputstrz(args[");
1274 if (wdt
< (void*).sizeof
*2) { lchar
= '0'; wdt
= cast(int)((void*).sizeof
)*2; signw
= ' '; }
1275 putRes("cwrxputhex(cast(usize)args[");
1279 } else static if (is(at
: long)) {
1280 putRes("cwrxputint(args[");
1284 putRes("import std.conv : to; cwrxputstr(to!string(args[");
1290 static if (is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar)) {
1291 putRes("cwrxputhex(cast(uint)args[");
1294 } else static if (is(at
== bool)) {
1295 putRes("cwrxputstr((args[");
1297 putRes("] ? `1` : `0`)");
1298 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
1299 assert(0, "can't hexprint floats yet");
1300 } else static if (is(at
: T
*, T
)) {
1301 if (wdt
< (void*).sizeof
*2) { lchar
= '0'; wdt
= cast(int)((void*).sizeof
)*2; signw
= ' '; }
1302 putRes("cwrxputhex(cast(usize)args[");
1305 } else static if (is(at
: long)) {
1306 putRes("cwrxputhex(args[");
1310 assert(0, "can't print '"~at
.stringof
~"' as hex");
1314 static if (is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar)) {
1315 putRes("cwrxputhex(cast(uint)args[");
1318 } else static if (is(at
== bool)) {
1319 putRes("cwrxputstr((args[");
1321 putRes("] ? `1` : `0`)");
1322 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
1323 assert(0, "can't hexprint floats yet");
1324 } else static if (is(at
: T
*, T
)) {
1325 if (wdt
< (void*).sizeof
*2) { lchar
= '0'; wdt
= cast(int)((void*).sizeof
)*2; signw
= ' '; }
1326 putRes("cwrxputhex(cast(usize)args[");
1329 } else static if (is(at
: long)) {
1330 putRes("cwrxputhex(args[");
1334 assert(0, "can't print '"~at
.stringof
~"' as hex");
1338 static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
1339 putRes("cwrxputfloat(args[");
1343 assert(0, "can't print '"~at
.stringof
~"' as float");
1346 default: assert(0, "invalid format specifier: '"~fmtch
~"'");
1351 putNum
!true(cast(uint)lchar
, 2);
1353 putNum
!true(cast(uint)rchar
, 2);
1360 while (pos
< fmt
.length
) {
1362 if (pos
>= fmt
.length
) break;
1363 assert(fmt
[pos
] == '%');
1365 if (pos
< fmt
.length
&& (fmt
[pos
] == '!' || fmt
[pos
] == '|')) { ++pos
; continue; } // skip rest
1366 if (pos
< fmt
.length
&& fmt
[pos
] == '<') {
1368 while (pos
< fmt
.length
) {
1369 if (fmt
[pos
] == '>') break;
1370 if (fmt
[pos
] == '\\') ++pos
;
1376 assert(0, "too many format specifiers");
1378 return res
[0..respos
];
1380 void conwritef (A args
) {
1381 //enum code = gen();
1382 //pragma(msg, code);
1383 //pragma(msg, "===========");
1385 scope(exit
) consoleWriteUnlock();
1391 // ////////////////////////////////////////////////////////////////////////// //
1394 //void conwritef(string fmt, A...) (A args) { fdwritef!(fmt)(args); }
1395 version(iv_cmdcon_honest_ctfe_writer
) {
1396 void conwritefln(string fmt
, A
...) (A args
) { conwritef
!(fmt
)(args
); cwrxputch('\n'); } /// write formatted string to console with compile-time format string
1397 void conwrite(A
...) (A args
) { conwritef
!("%|")(args
); } /// write formatted string to console with compile-time format string
1398 void conwriteln(A
...) (A args
) { conwritef
!("%|\n")(args
); } /// write formatted string to console with compile-time format string
1400 public void conwritef(string fmt
, A
...) (in auto ref A args
) { conprintfX
!false(fmt
, args
); } /// unhonest CTFE writer
1401 void conwritefln(string fmt
, A
...) (in auto ref A args
) { conprintfX
!true(fmt
, args
); } /// write formatted string to console with compile-time format string
1402 void conwrite(A
...) (in auto ref A args
) { conprintfX
!false("%|", args
); } /// write formatted string to console with compile-time format string
1403 void conwriteln(A
...) (in auto ref A args
) { conprintfX
!false("%|\n", args
); } /// write formatted string to console with compile-time format string
1407 // ////////////////////////////////////////////////////////////////////////// //
1408 version(conwriter_test
)
1411 override string
toString () const { return "{A}"; }
1414 char[] n
= ['x', 'y', 'z'];
1415 char[3] t
= "def";//['d', 'e', 'f'];
1416 conwriter("========================\n");
1417 conwritef
!"`%%`\n"();
1418 conwritef
!"`%-3s`\n"(42);
1419 conwritef
!"<`%3s`%%{str=%s}%|>\n"(cast(int)42, "[a]", new A(), n
, t
[]);
1420 //conwritefln!"<`%3@%3s`>%!"(cast(int)42, "[a]", new A(), n, t);
1421 //errwriteln("stderr");
1422 conwritefln
!"`%-3s`"(42);
1423 //conwritefln!"`%!z%-2@%-3s`%!"(69, 42, 666);
1424 //conwritefln!"`%!%1@%-3s%!`"(69, 42, 666);
1425 //conwritefln!"`%!%-1@%+0@%-3s%!`"(69, 42, 666);
1426 conwritefln
!"`%3.5s`"("a");
1427 conwritefln
!"`%7.5s`"("abcdefgh");
1428 conwritef
!"%|\n"(42, 666);
1429 conwritefln
!"`%+10.5s`"("abcdefgh");
1430 conwritefln
!"`%+10.-5s`"("abcdefgh");
1431 conwritefln
!"`%~++10.-5s`"("abcdefgh");
1432 conwritefln
!"`%~+~:+10.-5s`"("abcdefgh");
1433 //conwritef!"%\0<>\0|\n"(42, 666, 999);
1434 //conwritef!"%\0\t\0|\n"(42, 666, 999);
1435 conwritefln
!"`%~*05s %~.5s`"(42, 666);
1436 conwritef
!"`%s`\n"(t
);
1437 conwritef
!"`%08s`\n"("alice");
1438 conwritefln
!"#%08x"(16396);
1439 conwritefln
!"#%08X"(-16396);
1440 conwritefln
!"#%02X"(-16385);
1441 conwritefln
!"[%06s]"(-666);
1442 conwritefln
!"[%06s]"(cast(long)0x8000_0000_0000_0000uL);
1443 conwritefln
!"[%06x]"(cast(long)0x8000_0000_0000_0000uL);
1445 void wrflt () nothrow @nogc {
1446 conwriteln(42.666f);
1447 conwriteln(cast(double)42.666);
1451 //immutable char *strz = "stringz\0s";
1452 //conwritefln!"[%S]"(strz);
1456 // ////////////////////////////////////////////////////////////////////////// //
1457 /// dump variables. use like this: `mixin condump!("x", "y")` ==> "x = 5, y = 3"
1458 mixin template condump (Names
...) {
1459 auto _xdump_tmp_
= {
1460 import iv
.cmdcon
: conwrite
;
1461 foreach (/*auto*/ i
, /*auto*/ name
; Names
) conwrite(name
, " = ", mixin(name
), (i
< Names
.length
-1 ?
", " : "\n"));
1467 version(conwriter_test_dump
)
1473 mixin condump
!("x", "y"); // x = 5, y = 3
1474 mixin condump
!("z"); // z = 15
1475 mixin condump
!("x+y"); // x+y = 8
1476 mixin condump
!("x+y < z"); // x+y < z = true
1482 // ////////////////////////////////////////////////////////////////////////// //
1485 /// base console command class
1486 public class ConCommand
{
1488 public import std
.conv
: ConvException
, ConvOverflowException
;
1490 // this is hack to avoid allocating error exceptions
1491 // don't do that at home!
1493 __gshared ConvException exBadNum
;
1494 __gshared ConvException exBadStr
;
1495 __gshared ConvException exBadBool
;
1496 __gshared ConvException exBadInt
;
1497 __gshared ConvException exBadEnum
;
1498 __gshared ConvOverflowException exIntOverflow
;
1499 __gshared ConvException exBadHexEsc
;
1500 __gshared ConvException exBadEscChar
;
1501 __gshared ConvException exNoArg
;
1502 __gshared ConvException exTooManyArgs
;
1503 __gshared ConvException exBadArgType
;
1505 shared static this () {
1506 exBadNum
= new ConvException("invalid number");
1507 exBadStr
= new ConvException("invalid string");
1508 exBadBool
= new ConvException("invalid boolean");
1509 exBadInt
= new ConvException("invalid integer number");
1510 exBadEnum
= new ConvException("invalid enumeration value");
1511 exIntOverflow
= new ConvOverflowException("overflow in integral conversion");
1512 exBadHexEsc
= new ConvException("invalid hex escape");
1513 exBadEscChar
= new ConvException("invalid escape char");
1514 exNoArg
= new ConvException("argument expected");
1515 exTooManyArgs
= new ConvException("too many arguments");
1516 exBadArgType
= new ConvException("can't parse given argument type (internal error)");
1520 alias ArgCompleteCB
= void delegate (ConCommand self
); /// prototype for argument completion callback
1523 __gshared usize wordBufMem
; // buffer for `getWord()`
1524 __gshared
uint wordBufUsed
, wordBufSize
;
1525 ArgCompleteCB argcomplete
; // this delegate will be called to do argument autocompletion
1527 static void wbReset () nothrow @trusted @nogc { pragma(inline
, true); wordBufUsed
= 0; }
1529 static void wbPut (const(char)[] s
...) nothrow @trusted @nogc {
1530 if (s
.length
== 0) return;
1531 if (s
.length
> int.max
/4) assert(0, "oops: console command too long");
1532 if (wordBufUsed
+cast(uint)s
.length
> int.max
/4) assert(0, "oops: console command too long");
1533 // grow wordbuf, if necessary
1534 if (wordBufUsed
+cast(uint)s
.length
> wordBufSize
) {
1535 import core
.stdc
.stdlib
: realloc
;
1536 wordBufSize
= ((wordBufUsed
+cast(uint)s
.length
)|
0x3fff)+1;
1537 auto newmem
= cast(usize
)realloc(cast(void*)wordBufMem
, wordBufSize
);
1538 if (newmem
== 0) assert(0, "cmdcon: out of memory");
1539 wordBufMem
= cast(usize
)newmem
;
1541 assert(wordBufUsed
+cast(uint)s
.length
<= wordBufSize
);
1542 (cast(char*)(wordBufMem
+wordBufUsed
))[0..s
.length
] = s
;
1543 wordBufUsed
+= cast(uint)s
.length
;
1546 static @property const(char)[] wordBuf () nothrow @trusted @nogc { pragma(inline
, true); return (cast(char*)wordBufMem
)[0..wordBufUsed
]; }
1552 this (string aname
, string ahelp
=null) { name
= aname
; help
= ahelp
; } ///
1554 void showHelp () { conwriteln(name
, " -- ", help
); } ///
1557 /// cmdline doesn't contain command name
1558 void exec (ConString cmdline
) {
1559 auto w
= getWord(cmdline
);
1560 if (w
== "?") showHelp
;
1564 final @property ConCommand
completer (ArgCompleteCB cb
) { version(aliced
) pragma(inline
, true); argcomplete
= cb
; return this; }
1565 final @property ArgCompleteCB
completer () pure { version(aliced
) pragma(inline
, true); return argcomplete
; }
1568 /// parse ch as digit in given base. return -1 if ch is not a valid digit.
1569 int digit(TC
) (TC ch
, uint base
) pure nothrow @safe @nogc if (isSomeChar
!TC
) {
1571 if (ch
>= '0' && ch
<= '9') res
= ch
-'0';
1572 else if (ch
>= 'A' && ch
<= 'Z') res
= ch
-'A'+10;
1573 else if (ch
>= 'a' && ch
<= 'z') res
= ch
-'a'+10;
1575 return (res
>= base ?
-1 : res
);
1578 /** get word from string.
1580 * it will correctly parse quoted strings.
1582 * note that next call to `getWord()` can destroy result.
1583 * returns `null` if there are no more words.
1584 * `*strtemp` will be `true` if temporary string storage was used.
1586 ConString
getWord (ref ConString s
, bool *strtemp
=null) {
1588 if (strtemp
!is null) *strtemp
= false;
1590 while (s
.length
> 0 && s
.ptr
[0] <= ' ') s
= s
[1..$];
1591 if (s
.length
== 0) return null;
1593 if (s
.ptr
[0] == '"' || s
.ptr
[0] == '\'') {
1594 char qch
= s
.ptr
[0];
1597 bool hasSpecial
= false;
1598 while (pos
< s
.length
&& s
.ptr
[pos
] != qch
) {
1599 if (s
.ptr
[pos
] == '\\') { hasSpecial
= true; break; }
1602 // simple quoted string?
1604 auto res
= s
[0..pos
];
1605 if (pos
< s
.length
) ++pos
; // skip closing quote
1609 if (strtemp
!is null) *strtemp
= true;
1610 //wordBuf.assumeSafeAppend.length = pos;
1611 //if (pos) wordBuf[0..pos] = s[0..pos];
1612 if (pos
) wbPut(s
[0..pos
]);
1613 // process special chars
1614 while (pos
< s
.length
&& s
.ptr
[pos
] != qch
) {
1615 if (s
.ptr
[pos
] == '\\' && s
.length
-pos
> 1) {
1617 switch (s
.ptr
[pos
++]) {
1618 case '"': case '\'': case '\\': wbPut(s
.ptr
[pos
-1]); break;
1619 case '0': wbPut('\x00'); break;
1620 case 'a': wbPut('\a'); break;
1621 case 'b': wbPut('\b'); break;
1622 case 'e': wbPut('\x1b'); break;
1623 case 'f': wbPut('\f'); break;
1624 case 'n': wbPut('\n'); break;
1625 case 'r': wbPut('\r'); break;
1626 case 't': wbPut('\t'); break;
1627 case 'v': wbPut('\v'); break;
1630 foreach (immutable _
; 0..2) {
1631 if (pos
>= s
.length
) throw exBadHexEsc
;
1632 char c2
= s
.ptr
[pos
++];
1633 if (digit(c2
, 16) < 0) throw exBadHexEsc
;
1634 n
= n
*16+digit(c2
, 16);
1638 default: throw exBadEscChar
;
1642 wbPut(s
.ptr
[pos
++]);
1644 if (pos
< s
.length
) ++pos
; // skip closing quote
1650 while (pos
< s
.length
&& s
.ptr
[pos
] > ' ') ++pos
;
1651 auto res
= s
[0..pos
];
1657 /// parse value of type T.
1658 T
parseType(T
) (ref ConString s
) {
1659 import std
.utf
: byCodeUnit
;
1661 static if (is(UT
== enum)) {
1663 auto w
= getWord(s
);
1665 foreach (string mname
; __traits(allMembers
, UT
)) {
1666 if (mname
== w
) return __traits(getMember
, UT
, mname
);
1669 foreach (string mname
; __traits(allMembers
, UT
)) {
1670 if (strEquCI(mname
, w
)) return __traits(getMember
, UT
, mname
);
1673 if (w
.length
< 1) throw exBadEnum
;
1677 long num
= parseInt
!long(ns
);
1678 if (ns
.length
> 0) throw exBadEnum
;
1679 foreach (string mname
; __traits(allMembers
, UT
)) {
1680 if (__traits(getMember
, UT
, mname
) == num
) return __traits(getMember
, UT
, mname
);
1683 ulong num
= parseInt
!ulong(ns
);
1684 if (ns
.length
> 0) throw exBadEnum
;
1685 foreach (string mname
; __traits(allMembers
, UT
)) {
1686 if (__traits(getMember
, UT
, mname
) == num
) return __traits(getMember
, UT
, mname
);
1689 } catch (Exception
) {}
1691 } else static if (is(UT
== bool)) {
1693 auto w
= getWord(s
);
1694 if (w
is null) throw exNoArg
;
1696 auto res
= parseBool(w
, true, &good
);
1697 if (!good
) throw exBadBool
;
1699 } else static if ((isIntegral
!UT || isFloatingPoint
!UT
) && !is(UT
== enum)) {
1701 auto w
= getWord(s
);
1702 if (w
is null) throw exNoArg
;
1703 bool goodbool
= false;
1704 auto bv
= parseBool(w
, false, &goodbool
); // no numbers
1706 return cast(UT
)(bv ?
1 : 0);
1708 auto ss
= w
.byCodeUnit
;
1709 auto res
= parseNum
!UT(ss
);
1710 if (!ss
.empty
) throw exBadNum
;
1713 } else static if (is(UT
: ConString
)) {
1716 auto w
= getWord(s
);
1717 if (w
is null) throw exNoArg
;
1718 if (s
.length
&& s
.ptr
[0] > 32) throw exBadStr
;
1720 // temp storage was used
1721 static if (is(UT
== string
)) return w
.idup
; else return w
.dup
;
1723 // no temp storage was used
1724 static if (is(UT
== ConString
)) return w
;
1725 else static if (is(UT
== string
)) return w
.idup
;
1728 } else static if (is(UT
: char)) {
1731 auto w
= getWord(s
);
1732 if (w
is null || w
.length
!= 1) throw exNoArg
;
1733 if (s
.length
&& s
.ptr
[0] > 32) throw exBadStr
;
1740 /// parse boolean value
1741 public static bool parseBool (ConString s
, bool allowNumbers
=true, bool* goodval
=null) nothrow @trusted @nogc {
1743 if (goodval
!is null) *goodval
= false;
1744 while (s
.length
> 0 && s
[0] <= ' ') s
= s
[1..$];
1745 while (s
.length
> 0 && s
[$-1] <= ' ') s
= s
[0..$-1];
1746 if (s
.length
> tbuf
.length
) return false;
1748 foreach (char ch
; s
) {
1749 if (ch
>= 'A' && ch
<= 'Z') ch
+= 32; // poor man's tolower
1750 tbuf
.ptr
[pos
++] = ch
;
1752 switch (tbuf
[0..pos
]) {
1754 case "yes": case "tan":
1755 case "true": case "on":
1756 if (goodval
!is null) *goodval
= true;
1758 case "1": case "-1": case "42":
1760 if (goodval
!is null) *goodval
= true;
1765 case "no": case "ona":
1766 case "false": case "off":
1767 if (goodval
!is null) *goodval
= true;
1771 if (goodval
!is null) *goodval
= true;
1780 /** parse integer number.
1782 * parser checks for overflows and understands different bases (0x, 0b, 0o, 0d).
1783 * parser skips leading spaces. stops on first non-numeric char.
1787 * s = input range; will be modified
1793 * ConvException or ConvOverflowException
1795 public static T
parseInt(T
, TS
) (ref TS s
) if (isSomeChar
!(ElementType
!TS
) && isIntegral
!T
&& !is(T
== enum)) {
1796 import std
.traits
: isSigned
;
1799 static if (isSigned
!T
) bool neg = false;
1802 if (s
.front
> 32) break;
1805 if (s
.empty
) throw exBadInt
;
1808 case '+': // it's ok
1812 static if (isSigned
!T
) {
1819 default: // do nothing
1821 if (s
.empty
) throw exBadInt
;
1822 // check for various bases
1823 if (s
.front
== '0') {
1825 if (s
.empty
) return cast(T
)0;
1827 switch (/*auto ch = s.front*/ch
) {
1828 case 'b': case 'B': base
= 2; goto gotbase
;
1829 case 'o': case 'O': base
= 8; goto gotbase
;
1830 case 'd': case 'D': base
= 10; goto gotbase
;
1831 case 'x': case 'X': base
= 16;
1834 goto checkfirstdigit
;
1836 if (ch
!= '_' && digit(ch
, base
) < 0) throw exBadInt
;
1840 // no base specification; we want at least one digit
1842 if (s
.empty ||
digit(s
.front
, base
) < 0) throw exBadInt
;
1845 // we already know that the next char is valid
1846 bool needDigit
= false;
1849 int d
= digit(ch
, base
);
1851 if (needDigit
) throw exBadInt
;
1852 if (ch
!= '_') break;
1855 // funny overflow checks
1857 if ((num
*= base
) < onum
) throw exIntOverflow
;
1858 if ((num
+= d
) < onum
) throw exIntOverflow
;
1863 if (needDigit
) throw exBadInt
;
1864 // check underflow and overflow
1865 static if (isSigned
!T
) {
1866 long n
= cast(long)num
;
1868 // special case: negative 0x8000_0000_0000_0000uL is ok
1869 if (num
> 0x8000_0000_0000_0000uL) throw exIntOverflow
;
1870 if (num
!= 0x8000_0000_0000_0000uL) n
= -(n
);
1872 if (num
>= 0x8000_0000_0000_0000uL) throw exIntOverflow
;
1874 if (n
< T
.min || n
> T
.max
) throw exIntOverflow
;
1877 if (num
< T
.min || num
> T
.max
) throw exIntOverflow
;
1884 * parser checks for overflows and understands different integer bases (0x, 0b, 0o, 0d).
1885 * parser skips leading spaces. stops on first non-numeric char.
1889 * s = input range; will be modified
1895 * ConvException or ConvOverflowException
1897 public static T
parseNum(T
, TS
) (ref TS s
) if (isSomeChar
!(ElementType
!TS
) && (isIntegral
!T || isFloatingPoint
!T
) && !is(T
== enum)) {
1898 static if (isIntegral
!T
) {
1899 return parseInt
!T(s
);
1902 if (s
.front
> 32) break;
1905 import std
.conv
: stcparse
= parse
;
1906 return stcparse
!T(s
);
1910 public static bool checkHelp (scope ConString s
) {
1912 while (pos
< s
.length
&& s
.ptr
[pos
] <= 32) ++pos
;
1913 if (pos
== s
.length || s
.ptr
[pos
] != '?') return false;
1915 while (pos
< s
.length
&& s
.ptr
[pos
] <= 32) ++pos
;
1916 return (pos
>= s
.length
);
1919 public static bool hasArgs (scope ConString s
) {
1921 while (pos
< s
.length
&& s
.ptr
[pos
] <= 32) ++pos
;
1922 return (pos
< s
.length
);
1925 private static bool isBadStrChar() (char ch
) {
1926 pragma(inline
, true);
1927 return (ch
< ' ' || ch
== '\\' || ch
== '"' || ch
== '\'' || ch
== '#' || ch
== ';' || ch
> 126);
1930 public static void writeQuotedString(bool forceQuote
=true) (scope ConString s
) { quoteStringDG
!forceQuote(s
, delegate (scope ConString s
) { conwriter(s
); }); }
1932 public static void quoteStringDG(bool forceQuote
=false) (scope ConString s
, scope void delegate (scope ConString
) dg
) {
1933 if (dg
is null) return;
1934 static immutable string hexd
= "0123456789abcdef";
1935 static bool isBadStrChar() (char ch
) {
1936 pragma(inline
, true);
1937 return (ch
< ' ' || ch
== '\\' || ch
== '"' || ch
== '\'' || ch
== '#' || ch
== ';' || ch
> 126);
1940 if (s
.length
== 0) { dg(`""`); return; }
1941 static if (!forceQuote
) {
1942 bool needQuote
= false;
1943 foreach (char ch
; s
) if (ch
== ' ' ||
isBadStrChar(ch
)) { needQuote
= true; break; }
1944 if (!needQuote
) { dg(s
); return; }
1948 while (pos
< s
.length
) {
1950 while (end
< s
.length
&& !isBadStrChar(s
.ptr
[end
])) ++end
;
1951 if (end
> pos
) dg(s
[pos
..end
]);
1953 if (pos
>= s
.length
) break;
1955 switch (s
.ptr
[pos
++]) {
1956 case '"': case '\'': case '\\': dg(s
.ptr
[pos
-1..pos
]); break;
1957 case '\x00': dg("0"); break;
1958 case '\a': dg("a"); break;
1959 case '\b': dg("b"); break;
1960 case '\x1b': dg("e"); break;
1961 case '\f': dg("f"); break;
1962 case '\n': dg("n"); break;
1963 case '\r': dg("r"); break;
1964 case '\t': dg("t"); break;
1965 case '\v': dg("c"); break;
1967 ubyte c
= cast(ubyte)(s
.ptr
[pos
-1]);
1969 dg(hexd
[c
>>4..(c
>>4)+1]);
1970 dg(hexd
[c
&0x0f..(c
&0x0f)+1]);
1979 version(contest_parser
) unittest {
1980 auto cc
= new ConCommand("!");
1981 string s
= "this is 'a test' string \"you\tknow\" ";
1982 auto sc
= cast(ConString
)s
;
1984 auto w
= cc
.getWord(sc
);
1985 assert(w
== "this");
1988 auto w
= cc
.getWord(sc
);
1992 auto w
= cc
.getWord(sc
);
1993 assert(w
== "a test");
1996 auto w
= cc
.getWord(sc
);
1997 assert(w
== "string");
2000 auto w
= cc
.getWord(sc
);
2001 assert(w
== "you\tknow");
2004 auto w
= cc
.getWord(sc
);
2006 assert(sc
.length
== 0);
2009 import std
.conv
: ConvException
, ConvOverflowException
;
2010 import std
.exception
;
2011 import std
.math
: abs
;
2013 void testnum(T
) (string s
, T res
, int line
=__LINE__
) {
2014 import std
.string
: format
;
2017 import std
.utf
: byCodeUnit
;
2018 auto ss
= s
.byCodeUnit
;
2019 auto v
= ConCommand
.parseNum
!T(ss
);
2020 while (!ss
.empty
&& ss
.front
<= 32) ss
.popFront();
2021 if (!ss
.empty
) throw new ConvException("shit happens!");
2022 static assert(is(typeof(v
) == T
));
2023 static if (isIntegral
!T
) ok
= (v
== res
); else ok
= (abs(v
-res
) < T
.epsilon
);
2024 } catch (ConvException e
) {
2025 assert(0, format("unexpected exception thrown, called from line %s", line
));
2027 if (!ok
) assert(0, format("assertion failure, called from line %s", line
));
2030 void testbadnum(T
) (string s
, int line
=__LINE__
) {
2031 import std
.string
: format
;
2033 import std
.utf
: byCodeUnit
;
2034 auto ss
= s
.byCodeUnit
;
2035 auto v
= ConCommand
.parseNum
!T(ss
);
2036 while (!ss
.empty
&& ss
.front
<= 32) ss
.popFront();
2037 if (!ss
.empty
) throw new ConvException("shit happens!");
2038 } catch (ConvException e
) {
2041 assert(0, format("exception not thrown, called from line %s", line
));
2044 testnum
!int(" -42", -42);
2045 testnum
!int(" +42", 42);
2046 testnum
!int(" -4_2", -42);
2047 testnum
!int(" +4_2", 42);
2048 testnum
!int(" -0d42", -42);
2049 testnum
!int(" +0d42", 42);
2050 testnum
!int("0x2a", 42);
2051 testnum
!int("-0x2a", -42);
2052 testnum
!int("0o52", 42);
2053 testnum
!int("-0o52", -42);
2054 testnum
!int("0b00101010", 42);
2055 testnum
!int("-0b00101010", -42);
2056 testnum
!ulong("+9223372036854775808", 9223372036854775808uL);
2057 testnum
!long("9223372036854775807", 9223372036854775807);
2058 testnum
!long("-9223372036854775808", -9223372036854775808uL); // uL to workaround issue #13606
2059 testnum
!ulong("+0x8000_0000_0000_0000", 9223372036854775808uL);
2060 testnum
!long("-0x8000_0000_0000_0000", -9223372036854775808uL); // uL to workaround issue #13606
2061 testbadnum
!long("9223372036854775808");
2062 testbadnum
!int("_42");
2063 testbadnum
!int("42_");
2064 testbadnum
!int("42_ ");
2065 testbadnum
!int("4__2");
2066 testbadnum
!int("0x_2a");
2067 testbadnum
!int("-0x_2a");
2068 testbadnum
!int("_0x2a");
2069 testbadnum
!int("-_0x2a");
2070 testbadnum
!int("_00x2a");
2071 testbadnum
!int("-_00x2a");
2072 testbadnum
!int(" +0x");
2074 testnum
!int("666", 666);
2075 testnum
!int("+666", 666);
2076 testnum
!int("-666", -666);
2078 testbadnum
!int("+");
2079 testbadnum
!int("-");
2080 testbadnum
!int("5a");
2082 testbadnum
!int("5.0");
2083 testbadnum
!int("5e+2");
2085 testnum
!uint("666", 666);
2086 testnum
!uint("+666", 666);
2087 testbadnum
!uint("-666");
2089 testnum
!int("0x29a", 666);
2090 testnum
!int("0X29A", 666);
2091 testnum
!int("-0x29a", -666);
2092 testnum
!int("-0X29A", -666);
2093 testnum
!int("0b100", 4);
2094 testnum
!int("0B100", 4);
2095 testnum
!int("-0b100", -4);
2096 testnum
!int("-0B100", -4);
2097 testnum
!int("0o666", 438);
2098 testnum
!int("0O666", 438);
2099 testnum
!int("-0o666", -438);
2100 testnum
!int("-0O666", -438);
2101 testnum
!int("0d666", 666);
2102 testnum
!int("0D666", 666);
2103 testnum
!int("-0d666", -666);
2104 testnum
!int("-0D666", -666);
2106 testnum
!byte("-0x7f", -127);
2107 testnum
!byte("-0x80", -128);
2108 testbadnum
!byte("0x80");
2109 testbadnum
!byte("-0x81");
2111 testbadnum
!uint("1a");
2112 testbadnum
!uint("0x1g");
2113 testbadnum
!uint("0b12");
2114 testbadnum
!uint("0o78");
2115 testbadnum
!uint("0d1f");
2117 testbadnum
!int("0x_2__9_a__");
2118 testbadnum
!uint("0x_");
2120 testnum
!ulong("0x8000000000000000", 0x8000000000000000UL
);
2121 testnum
!long("-0x8000000000000000", -0x8000000000000000uL
);
2122 testbadnum
!long("0x8000000000000000");
2123 testbadnum
!ulong("0x80000000000000001");
2124 testbadnum
!long("-0x8000000000000001");
2126 testbadnum
!float("-0O666");
2127 testnum
!float("0x666p0", 1638.0f);
2128 testnum
!float("-0x666p0", -1638.0f);
2129 testnum
!double("+1.1e+2", 110.0);
2130 testnum
!double("2.4", 2.4);
2131 testnum
!double("1_2.4", 12.4);
2132 testnum
!float("42666e-3", 42.666f);
2133 testnum
!float(" 4.2 ", 4.2);
2135 conwriteln("console: parser test passed");
2139 // ////////////////////////////////////////////////////////////////////////// //
2141 public final class ConAlias
{
2143 public import std
.conv
: ConvOverflowException
;
2144 __gshared ConvOverflowException exAliasTooBig
;
2145 __gshared ConvOverflowException exAliasTooDeep
;
2147 shared static this () {
2148 exAliasTooBig
= new ConvOverflowException("alias text too big");
2149 exAliasTooDeep
= new ConvOverflowException("alias expansion too deep");
2156 this (const(char)[] atext
) @nogc {
2161 import core
.stdc
.stdlib
: free
;
2162 if (textmem
!= 0) free(cast(void*)textmem
);
2165 const(char)[] get () nothrow @trusted @nogc { pragma(inline
, true); return (textmem
!= 0 ?
(cast(char*)textmem
)[0..textsize
] : null); }
2167 void set (const(char)[] atext
) @nogc {
2168 if (atext
.length
> 65535) throw exAliasTooBig
;
2170 if (atext
.length
> allocsize
) {
2171 import core
.stdc
.stdlib
: realloc
;
2173 if (atext
.length
<= 4096) newsz
= 4096;
2174 else if (atext
.length
<= 8192) newsz
= 8192;
2175 else if (atext
.length
<= 16384) newsz
= 16384;
2176 else if (atext
.length
<= 32768) newsz
= 32768;
2178 assert(newsz
>= atext
.length
);
2179 auto newmem
= cast(usize
)realloc(cast(void*)textmem
, newsz
);
2180 if (newmem
== 0) assert(0, "out of memory for alias"); //FIXME
2184 assert(allocsize
>= atext
.length
);
2185 textsize
= cast(uint)atext
.length
;
2186 (cast(char*)textmem
)[0..textsize
] = atext
;
2191 // ////////////////////////////////////////////////////////////////////////// //
2192 /// convar attributes
2193 public enum ConVarAttr
: uint {
2195 Archive
= 1U<<0, /// save on change (saving must be done by library user)
2196 Hex
= 1U<<1, /// dump this variable as hex value (valid for integrals)
2198 User
= 1U<<30, /// user-created
2199 ReadOnly
= 1U<<31, /// can't be changed with console command (direct change is still possible)
2203 /// variable of some type
2204 public class ConVarBase
: ConCommand
{
2209 alias PreChangeHookCB
= bool delegate (ConVarBase self
, ConString newval
); /// prototype for "before value change hook"; return `false` to abort; `newval` is not parsed
2210 alias PostChangeHookCB
= void delegate (ConVarBase self
, ConString newval
); /// prototype for "after value change hook"; `newval` is not parsed
2213 PreChangeHookCB hookBeforeChange
;
2214 PostChangeHookCB hookAfterChange
;
2217 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); } ///
2219 /// replaces current attributes (can't set `ConVarAttr.User` w/o !true)
2220 final void setAttrs(bool any
=false) (const(ConVarAttr
)[] attrs
...) pure nothrow @safe @nogc {
2222 foreach (const ConVarAttr a
; attrs
) {
2224 if (a
== ConVarAttr
.User
) continue;
2230 final ConVarBase
setBeforeHook (PreChangeHookCB cb
) { hookBeforeChange
= cb
; return this; } /// this can be chained
2231 final ConVarBase
setAfterHook (PostChangeHookCB cb
) { hookAfterChange
= cb
; return this; } /// this can be chained
2233 @property pure nothrow @safe @nogc final {
2234 PreChangeHookCB
beforeHook () { return hookBeforeChange
; } ///
2235 PostChangeHookCB
afterHook () { return hookAfterChange
; } ///
2237 void beforeHook (PreChangeHookCB cb
) { hookBeforeChange
= cb
; } ///
2238 void afterHook (PostChangeHookCB cb
) { hookAfterChange
= cb
; } ///
2240 /// attributes (see ConVarAttr enum)
2241 uint attrs () const { pragma(inline
, true); return mAttrs
; }
2242 /// replaces current attributes (can't set/remove `ConVarAttr.User` w/o !true)
2243 void attrs(bool any
=false) (uint v
) {
2244 pragma(inline
, true);
2245 static if (any
) mAttrs
= v
; else mAttrs
= v
&~(ConVarAttr
.User
);
2248 bool attrArchive () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.Archive
) != 0); } ///
2249 bool attrNoArchive () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.Archive
) == 0); } ///
2251 void attrArchive (bool v
) { pragma(inline
, true); if (v
) mAttrs |
= ConVarAttr
.Archive
; else mAttrs
&= ~ConVarAttr
.Archive
; } ///
2252 void attrNoArchive (bool v
) { pragma(inline
, true); if (!v
) mAttrs |
= ConVarAttr
.Archive
; else mAttrs
&= ~ConVarAttr
.Archive
; } ///
2254 bool attrHexDump () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.Hex
) != 0); } ///
2256 bool attrReadOnly () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.ReadOnly
) != 0); } ///
2257 void attrReadOnly (bool v
) { pragma(inline
, true); if (v
) mAttrs |
= ConVarAttr
.ReadOnly
; else mAttrs
&= ~ConVarAttr
.ReadOnly
; } ///
2259 bool attrUser () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.User
) != 0); } ///
2262 abstract void printValue (); /// print variable value to console
2263 abstract bool isString () const pure nothrow @nogc; /// is this string variable?
2264 abstract ConString
strval () /*nothrow @nogc*/; /// get string value (for any var)
2266 /// get variable value, converted to the given type (if it is possible).
2267 @property T
value(T
) () /*nothrow @nogc*/ {
2268 pragma(inline
, true);
2269 static if (is(T
== enum)) {
2270 return cast(T
)getIntValue
;
2271 } else static if (is(T
: ulong)) {
2272 // integer, xchar, boolean
2273 return cast(T
)getIntValue
;
2274 } else static if (is(T
: double)) {
2276 return cast(T
)getDoubleValue
;
2277 } else static if (is(T
: ConString
)) {
2279 static if (is(T
== string
)) return strval
.idup
;
2280 else static if (is(T
== char[])) return strval
.dup
;
2288 /// set variable value, converted from the given type (if it is possible).
2289 /// ReadOnly is ignored, no hooks will be called.
2290 @property void value(T
) (T val
) /*nothrow*/ {
2291 pragma(inline
, true);
2292 static if (is(T
: ulong)) {
2293 // integer, xchar, boolean
2294 setIntValue(cast(ulong)val
, isSigned
!T
);
2295 } else static if (is(T
: double)) {
2297 setDoubleValue(cast(double)val
);
2298 } else static if (is(T
: ConString
)) {
2299 static if (is(T
== string
)) setStrValue(val
); else setCCharValue(val
);
2301 static assert(0, "invalid type");
2306 abstract ulong getIntValue () /*nothrow @nogc*/;
2307 abstract double getDoubleValue () /*nothrow @nogc*/;
2309 abstract void setIntValue (ulong v
, bool signed
) /*nothrow @nogc*/;
2310 abstract void setDoubleValue (double v
) /*nothrow @nogc*/;
2311 abstract void setStrValue (string v
) /*nothrow*/;
2312 abstract void setCCharValue (ConString v
) /*nothrow*/;
2316 // ////////////////////////////////////////////////////////////////////////// //
2317 /// console will use this to register console variables
2318 final class ConVar(T
) : ConVarBase
{
2320 enum useAtomic
= is(T
== shared);
2322 static if (isIntegral
!TT
) {
2325 } else static if (isFloatingPoint
!TT
) {
2329 static if (!is(TT
: ConString
)) {
2332 char[256] tvbuf
; // temp value
2335 TT
delegate (ConVarBase self
) cvGetter
;
2336 void delegate (ConVarBase self
, TT nv
) cvSetter
;
2338 void delegate (ConVarBase self
, TT pv
, TT nv
) hookAfterChangeVV
;
2340 this (T
* avptr
, string aname
, string ahelp
=null) {
2342 super(aname
, ahelp
);
2345 static if (isIntegral
!TT || isFloatingPoint
!TT
) {
2346 this (T
* avptr
, TT aminv
, TT amaxv
, string aname
, string ahelp
=null) {
2350 super(aname
, ahelp
);
2354 /// this method will respect `ReadOnly` flag, and will call before/after hooks.
2355 override void exec (ConString cmdline
) {
2356 if (checkHelp(cmdline
)) { showHelp
; return; }
2357 if (!hasArgs(cmdline
)) { printValue
; return; }
2358 if (attrReadOnly
) return; // can't change read-only var with console commands
2359 static if ((is(TT
== bool) || isIntegral
!TT || isFloatingPoint
!TT
) && !is(TT
== enum)) {
2360 while (cmdline
.length
&& cmdline
[0] <= 32) cmdline
= cmdline
[1..$];
2361 while (cmdline
.length
&& cmdline
[$-1] <= 32) cmdline
= cmdline
[0..$-1];
2362 if (cmdline
== "toggle") {
2363 if (hookBeforeChange
!is null) { if (!hookBeforeChange(this, cmdline
)) return; }
2364 auto prval
= getv();
2365 if (cvSetter
!is null) {
2366 cvSetter(this, !prval
);
2368 static if (useAtomic
) {
2370 atomicStore(*vptr
, !prval
);
2375 if (hookAfterChangeVV
!is null) hookAfterChangeVV(this, prval
, !prval
);
2376 if (hookAfterChange
!is null) hookAfterChange(this, cmdline
);
2380 auto newvals
= cmdline
;
2381 TT val
= parseType
!TT(/*ref*/ cmdline
);
2382 if (hasArgs(cmdline
)) throw exTooManyArgs
;
2383 static if (isIntegral
!TT
) {
2384 if (val
< minv
) val
= minv
;
2385 if (val
> maxv
) val
= maxv
;
2387 if (hookBeforeChange
!is null) { if (!hookBeforeChange(this, newvals
)) return; }
2389 if (hookAfterChangeVV
!is null) oval
= getv();
2390 if (cvSetter
!is null) {
2391 cvSetter(this, val
);
2393 static if (useAtomic
) {
2395 atomicStore(*vptr
, val
);
2400 if (hookAfterChangeVV
!is null) hookAfterChangeVV(this, oval
, val
);
2401 if (hookAfterChange
!is null) hookAfterChange(this, newvals
);
2404 override bool isString () const pure nothrow @nogc {
2405 static if (is(TT
: ConString
)) return true; else return false;
2408 final private TT
getv() () /*nothrow @nogc*/ {
2409 pragma(inline
, true);
2411 if (cvGetter
!is null) {
2412 return cvGetter(this);
2414 static if (useAtomic
) return atomicLoad(*vptr
); else return *vptr
;
2418 override ConString
strval () /*nothrow @nogc*/ {
2419 //conwriteln("*** strval for '", name, "'");
2420 import core
.stdc
.stdio
: snprintf
;
2421 static if (is(TT
== enum)) {
2423 foreach (string mname
; __traits(allMembers
, TT
)) {
2424 if (__traits(getMember
, TT
, mname
) == v
) return mname
;
2427 } else static if (is(T
: ConString
)) {
2429 } else static if (is(XUQQ
!T
== bool)) {
2430 return (getv() ?
"tan" : "ona");
2431 } else static if (isIntegral
!T
) {
2432 static if (isSigned
!T
) {
2433 auto len
= snprintf(vbuf
.ptr
, vbuf
.length
, "%lld", cast(long)(getv()));
2435 auto len
= snprintf(vbuf
.ptr
, vbuf
.length
, (attrHexDump ?
"0x%06llx".ptr
: "%llu".ptr
), cast(ulong)(getv()));
2437 return (len
>= 0 ? vbuf
[0..len
] : "?");
2438 } else static if (isFloatingPoint
!T
) {
2439 auto len
= snprintf(vbuf
.ptr
, vbuf
.length
, "%g", cast(double)(getv()));
2440 return (len
>= 0 ? vbuf
[0..len
] : "?");
2441 } else static if (is(XUQQ
!T
== char)) {
2442 vbuf
.ptr
[0] = cast(char)getv();
2445 static assert(0, "can't get string value of convar with type '"~T
.stringof
~"'");
2449 protected override ulong getIntValue () /*nothrow @nogc*/ {
2450 static if (is(T
: ulong) ||
is(T
: double)) return cast(ulong)(getv()); else return ulong.init
;
2453 protected override double getDoubleValue () /*nothrow @nogc*/ {
2454 static if (is(T
: double) ||
is(T
: ulong)) return cast(double)(getv()); else return double.init
;
2457 private template PutVMx(string val
) {
2458 static if (useAtomic
) {
2459 enum PutVMx
= "{ import core.atomic; if (cvSetter !is null) cvSetter(this, "~val
~"); else atomicStore(*vptr, "~val
~"); }";
2461 enum PutVMx
= "{ if (cvSetter !is null) cvSetter(this, "~val
~"); else *vptr = "~val
~"; }";
2465 protected override void setIntValue (ulong v
, bool signed
) /*nothrow @nogc*/ {
2467 static if (is(XUQQ
!T
== enum)) {
2468 foreach (string mname
; __traits(allMembers
, TT
)) {
2469 if (__traits(getMember
, TT
, mname
) == v
) {
2470 mixin(PutVMx
!"cast(T)v");
2475 conwriteln("invalid enum value '", v
, "' for variable '", name
, "'");
2476 } else static if (is(T
: ulong) ||
is(T
: double)) {
2477 mixin(PutVMx
!"cast(T)v");
2478 } else static if (is(T
: ConString
)) {
2479 import core
.stdc
.stdio
: snprintf
;
2480 auto len
= snprintf(tvbuf
.ptr
, tvbuf
.length
, (signed ?
"%lld" : "%llu"), v
);
2481 static if (is(T
== string
)) mixin(PutVMx
!"cast(string)(tvbuf[0..len])"); // not really safe, but...
2482 else static if (is(T
== ConString
)) mixin(PutVMx
!"cast(ConString)(tvbuf[0..len])");
2483 else static if (is(T
== char[])) mixin(PutVMx
!"tvbuf[0..len]");
2487 protected override void setDoubleValue (double v
) /*nothrow @nogc*/ {
2489 static if (is(XUQQ
!T
== enum)) {
2490 foreach (string mname
; __traits(allMembers
, TT
)) {
2491 if (__traits(getMember
, TT
, mname
) == v
) {
2492 mixin(PutVMx
!"cast(T)v");
2497 conwriteln("invalid enum value '", v
, "' for variable '", name
, "'");
2498 } else static if (is(T
: ulong) ||
is(T
: double)) {
2499 mixin(PutVMx
!"cast(T)v");
2500 } else static if (is(T
: ConString
)) {
2501 import core
.stdc
.stdio
: snprintf
;
2502 auto len
= snprintf(tvbuf
.ptr
, tvbuf
.length
, "%g", v
);
2503 static if (is(T
== string
)) mixin(PutVMx
!"cast(string)(tvbuf[0..len])"); // not really safe, but...
2504 else static if (is(T
== ConString
)) mixin(PutVMx
!"cast(ConString)(tvbuf[0..len])");
2505 else static if (is(T
== char[])) mixin(PutVMx
!"tvbuf[0..len]");
2509 protected override void setStrValue (string v
) /*nothrow*/ {
2511 static if (is(XUQQ
!T
== enum)) {
2514 auto vv
= ConCommand
.parseType
!T(ss
);
2515 mixin(PutVMx
!"cast(T)vv");
2516 } catch (Exception
) {
2517 conwriteln("invalid enum value '", v
, "' for variable '", name
, "'");
2519 } else static if (is(T
== string
) ||
is(T
== ConString
)) {
2520 mixin(PutVMx
!"cast(T)v");
2521 } else static if (is(T
== char[])) {
2522 mixin(PutVMx
!"v.dup");
2526 protected override void setCCharValue (ConString v
) /*nothrow*/ {
2528 static if (is(XUQQ
!T
== enum)) {
2531 auto vv
= ConCommand
.parseType
!T(ss
);
2532 mixin(PutVMx
!"cast(T)vv");
2533 } catch (Exception
) {
2534 conwriteln("invalid enum value '", v
, "' for variable '", name
, "'");
2537 else static if (is(T
== string
)) mixin(PutVMx
!"v.idup");
2538 else static if (is(T
== ConString
)) mixin(PutVMx
!"v");
2539 else static if (is(T
== char[])) mixin(PutVMx
!"v.dup");
2542 override void printValue () {
2543 static if (is(T
== enum)) {
2545 foreach (string mname
; __traits(allMembers
, TT
)) {
2546 if (__traits(getMember
, TT
, mname
) == vx
) {
2549 //writeQuotedString(mname);
2556 conwriteln(name
, " ", cast(ulong)getv());
2557 } else static if (is(T
: ConString
)) {
2560 writeQuotedString(getv());
2562 } else static if (is(XUQQ
!T
== char)) {
2565 char[1] st
= getv();
2566 if (st
[0] <= ' ' || st
[0] == 127 || st
[0] == '"' || st
[0] == '\\') {
2567 writeQuotedString(st
[]);
2574 } else static if (is(T
== bool)) {
2575 conwriteln(name
, " ", (getv() ?
"tan" : "ona"));
2577 conwriteln(name
, " ", strval
);
2584 version(contest_vars
) unittest {
2585 __gshared
int vi
= 42;
2586 __gshared string vs
= "str";
2587 __gshared
bool vb
= true;
2588 auto cvi
= new ConVar
!int(&vi
, "vi", "integer variable");
2589 auto cvs
= new ConVar
!string(&vs
, "vs", "string variable");
2590 auto cvb
= new ConVar
!bool(&vb
, "vb", "bool variable");
2598 cvs
.exec("'fo\to'");
2601 conwriteln("vi=", vi
);
2602 conwriteln("vs=[", vs
, "]");
2612 // don't use std.algo
2613 private void heapsort(alias lessfn
, T
) (T
[] arr
) {
2614 auto count
= arr
.length
;
2615 if (count
< 2) return; // nothing to do
2617 void siftDown(T
) (T start
, T end
) {
2620 auto child
= 2*root
+1; // left child
2621 if (child
> end
) break;
2623 if (lessfn(arr
.ptr
[swap
], arr
.ptr
[child
])) swap
= child
;
2624 if (child
+1 <= end
&& lessfn(arr
.ptr
[swap
], arr
.ptr
[child
+1])) swap
= child
+1;
2625 if (swap
== root
) break;
2626 auto tmp
= arr
.ptr
[swap
];
2627 arr
.ptr
[swap
] = arr
.ptr
[root
];
2628 arr
.ptr
[root
] = tmp
;
2635 auto start
= (end
-1)/2; // parent; always safe, as our array has at least two items
2637 siftDown(start
, end
);
2638 if (start
-- == 0) break; // as `start` cannot be negative, use this condition
2643 auto tmp
= arr
.ptr
[0];
2644 arr
.ptr
[0] = arr
.ptr
[end
];
2653 private void conUnsafeArrayAppend(T
) (ref T
[] arr
, auto ref T v
) /*nothrow*/ {
2654 if (arr
.length
>= int.max
/2) assert(0, "too many elements in array");
2655 auto optr
= arr
.ptr
;
2657 if (arr
.ptr
!is optr
) {
2658 import core
.memory
: GC
;
2660 if (optr
is GC
.addrOf(optr
)) GC
.setAttr(optr
, GC
.BlkAttr
.NO_INTERIOR
);
2665 private void addName (string name
) {
2666 //{ import core.stdc.stdio; printf("%.*s\n", cast(uint)name.length, name.ptr); }
2668 static bool strLessCI (const(char)[] s0
, const(char)[] s1
) pure nothrow @trusted @nogc {
2669 auto slen
= s0
.length
;
2670 if (slen
> s1
.length
) slen
= s1
.length
;
2672 foreach (immutable idx
, char c0
; s0
[0..slen
]) {
2673 if (c0
>= 'A' && c0
<= 'Z') c0
+= 32; // poor man's tolower()
2675 if (c1
>= 'A' && c1
<= 'Z') c1
+= 32; // poor man's tolower()
2676 if (c0
< c1
) return true;
2677 if (c0
> c1
) return false;
2679 if (s0
.length
< s1
.length
) return true;
2680 if (s0
.length
> s1
.length
) return false;
2684 if (name
.length
== 0) return;
2685 if (name
!in cmdlist
) {
2686 //cmdlistSorted ~= name;
2687 cmdlistSorted
.conUnsafeArrayAppend(name
);
2688 heapsort
!strLessCI(cmdlistSorted
);
2693 // ////////////////////////////////////////////////////////////////////////// //
2696 void concmdRangeCompleter(IR
, bool casesens
=true) (ConCommand self
, auto ref IR rng
) if (isForwardRange
!IR
&& is(ElementEncodingType
!IR
: const(char)[])) {
2697 if (rng
.empty
) return; // oops
2698 auto cs
= conInputBuffer
[0..conInputBufferCurX
];
2699 ConCommand
.getWord(cs
); // skip command
2700 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2701 if (cs
.length
== 0) {
2702 conwriteln(self
.name
, ":");
2703 foreach (const(char)[] mname
; rng
) conwriteln(" ", mname
);
2705 if (cs
[0] == '"' || cs
[0] == '\'') return; // alas
2706 ConString pfx
= ConCommand
.getWord(cs
);
2707 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2708 if (cs
.length
) return; // alas
2709 const(char)[] bestPfx
;
2711 foreach (const(char)[] mname
; rng
.save
) {
2712 if (mname
.length
>= pfx
.length
&& strEquCI(mname
[0..pfx
.length
], pfx
)) {
2716 //if (mname.length < bestPfx.length) bestPfx = bestPfx[0..mname.length];
2718 while (pos
< bestPfx
.length
&& pos
< mname
.length
) {
2719 char c0
= bestPfx
[pos
];
2720 char c1
= mname
[pos
];
2721 static if (!casesens
) {
2722 if (c0
>= 'A' && c0
<= 'Z') c0
+= 32; // poor man's tolower()
2723 if (c1
>= 'A' && c1
<= 'Z') c1
+= 32; // poor man's tolower()
2725 if (c0
!= c1
) break;
2728 if (pos
< bestPfx
.length
) bestPfx
= bestPfx
[0..pos
];
2733 if (count
== 0 || bestPfx
.length
< pfx
.length
) { conwriteln(self
.name
, ": ???"); return; }
2734 foreach (char ch
; bestPfx
[pfx
.length
..$]) conAddInputChar(ch
);
2736 conAddInputChar(' ');
2738 conwriteln(self
.name
, ":");
2739 foreach (const(char)[] mname
; rng
) {
2740 if (mname
.length
>= bestPfx
.length
&& mname
[0..bestPfx
.length
] == bestPfx
) {
2741 conwriteln(" ", mname
);
2749 // ////////////////////////////////////////////////////////////////////////// //
2750 /// helper function for [cmdconSetFileCompleter]
2751 public string
[] cmdconFilesList (string pfx
, scope bool delegate (ConString name
) nothrow isGood
=null) nothrow {
2752 static bool startsWith (const(char)[] str, const(char)[] prefix
) pure nothrow @trusted @nogc {
2753 if (prefix
.length
== 0) return true;
2754 if (prefix
.length
> str.length
) return false;
2755 return (str[0..prefix
.length
] == prefix
);
2762 if (pfx
.length
== 0) {
2765 if (pfx
[$-1] == '/') {
2773 string
xxname (string fn
, bool asDir
=false) {
2774 if (fn
== "/") return fn
;
2776 if (fn
== ".") return "./";
2778 if (startsWith(fn
, "./")) fn
= fn
[2..$];
2779 if (asDir
) fn
~= "/";
2783 foreach (DirEntry
de; dirEntries(path
, SpanMode
.shallow
)) {
2784 if (de.baseName
.length
== 0) continue;
2785 if (de.baseName
[0] == '.') continue;
2788 if (isGood
is null ||
isGood(de.name
)) {
2789 if (pfx
.length
== 0 ||
startsWith(de.baseName
, pfx
)) res
~= xxname(de.name
);
2791 } else if (de.isDir
) {
2792 if (pfx
.length
== 0 ||
startsWith(de.baseName
, pfx
)) res
~= xxname(de.name
, true);
2794 } catch (Exception e
) {} // sorry
2796 } catch (Exception e
) {} // sorry
2797 import std
.algorithm
: sort
;
2798 res
.sort
!((string a
, string b
) {
2799 if (a
[$-1] == '/') {
2800 if (b
[$-1] != '/') return true;
2803 if (b
[$-1] == '/') return false;
2810 /// if `getCurrentFile` is not null, and sole "!" is given as argument, current file will be put
2811 public void cmdconSetFileCompleter (ConCommand cmd
, string
delegate () nothrow getCurrentFile
=null, bool delegate (ConString name
) nothrow isGood
=null) {
2812 if (cmd
is null) return;
2813 cmd
.completer
= delegate (ConCommand self
) {
2814 auto cs
= conInputBuffer
[0..conInputBufferCurX
];
2815 ConCommand
.getWord(cs
); // skip command
2816 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2817 // put currently loaded file to buffer
2818 if (cs
== "!" && getCurrentFile
!is null) {
2819 string cfn
= getCurrentFile();
2821 conAddInputChar(ConInputChar
.Backspace
); // remove "!"
2822 foreach (char ch
; cfn
) conAddInputChar(ch
);
2826 string
[] list
= cmdconFilesList(cs
.idup
, isGood
);
2827 if (list
.length
== 0) {
2828 conwriteln(self
.name
, ": no matching files");
2831 auto lc
= conInputLastChange();
2832 self
.concmdRangeCompleter(list
);
2833 // if we ended with "/ ", remove space
2834 if (lc
!= conInputLastChange()) {
2835 cs
= conInputBuffer
[0..conInputBufferCurX
];
2836 if (cs
.length
> 2 && cs
[$-1] == ' ' && cs
[$-2] == '/') {
2837 conAddInputChar(ConInputChar
.Backspace
);
2844 // ////////////////////////////////////////////////////////////////////////// //
2845 private void enumComplete(T
) (ConCommand self
) if (is(T
== enum)) {
2846 auto cs
= conInputBuffer
[0..conInputBufferCurX
];
2847 ConCommand
.getWord(cs
); // skip command
2848 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2849 if (cs
.length
== 0) {
2850 conwriteln(self
.name
, ":");
2851 foreach (string mname
; __traits(allMembers
, T
)) conwriteln(" ", mname
);
2853 if (cs
[0] == '"' || cs
[0] == '\'') return; // alas
2854 ConString pfx
= ConCommand
.getWord(cs
);
2855 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2856 if (cs
.length
) return; // alas
2859 foreach (string mname
; __traits(allMembers
, T
)) {
2860 if (mname
.length
>= pfx
.length
&& strEquCI(mname
[0..pfx
.length
], pfx
)) {
2864 //if (mname.length < bestPfx.length) bestPfx = bestPfx[0..mname.length];
2866 while (pos
< bestPfx
.length
&& pos
< mname
.length
) {
2867 char c0
= bestPfx
[pos
];
2868 char c1
= mname
[pos
];
2869 if (c0
>= 'A' && c0
<= 'Z') c0
+= 32; // poor man's tolower()
2870 if (c1
>= 'A' && c1
<= 'Z') c1
+= 32; // poor man's tolower()
2871 if (c0
!= c1
) break;
2874 if (pos
< bestPfx
.length
) bestPfx
= bestPfx
[0..pos
];
2879 if (count
== 0 || bestPfx
.length
< pfx
.length
) { conwriteln(self
.name
, ": ???"); return; }
2880 foreach (char ch
; bestPfx
[pfx
.length
..$]) conAddInputChar(ch
);
2882 conAddInputChar(' ');
2884 conwriteln(self
.name
, ":");
2885 foreach (string mname
; __traits(allMembers
, T
)) {
2886 if (mname
.length
>= bestPfx
.length
&& mname
[0..bestPfx
.length
] == bestPfx
) {
2887 conwriteln(" ", mname
);
2895 private void boolComplete(T
) (ConCommand self
) if (is(T
== bool)) {
2896 auto cs
= conInputBuffer
[0..conInputBufferCurX
];
2897 ConCommand
.getWord(cs
); // skip command
2898 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2899 if (cs
.length
> 0) {
2900 enum Cmd
= "toggle";
2901 foreach (immutable idx
, char ch
; Cmd
) {
2902 if (idx
>= cs
.length
) break;
2903 if (cs
[idx
] != ch
) return; // alas
2905 if (cs
.length
> Cmd
.length
) return;
2906 foreach (char ch
; Cmd
[cs
.length
..$]) conAddInputChar(ch
);
2907 conAddInputChar(' ');
2909 conwriteln(" toggle?");
2914 /** register console variable with getter and setter.
2917 * aname = variable name
2921 * attrs = convar attributes (see `ConVarAttr`)
2923 public ConVarBase
conRegVar(T
) (string aname
, string ahelp
, T
delegate (ConVarBase self
) dgg
, void delegate (ConVarBase self
, T nv
) dgs
, const(ConVarAttr
)[] attrs
...)
2924 if (!is(T
== struct) && !is(T
== class))
2926 if (dgg
is null || dgs
is null) assert(0, "cmdcon: both getter and setter should be defined for convar");
2927 if (aname
.length
> 0) {
2929 auto cv
= new ConVar
!T(null, aname
, ahelp
);
2933 cmdlist
[aname
] = cv
;
2941 private enum RegVarMixin(string cvcreate
) =
2942 `static assert(isGoodVar!v, "console variable '"~v.stringof~"' must be shared or __gshared");`~
2943 `if (aname.length == 0) {`~
2944 `aname = (&v).stringof[2..$]; /*HACK*/`~
2945 ` foreach_reverse (immutable cidx, char ch; aname) if (ch == '.') { aname = aname[cidx+1..$]; break; }`~
2947 `if (aname.length > 0) {`~
2949 ` auto cv = `~cvcreate
~`;`~
2950 ` cv.setAttrs(attrs);`~
2951 ` alias UT = XUQQ!(typeof(v));`~
2952 ` static if (is(UT == enum)) {`~
2953 ` import std.functional : toDelegate;`~
2954 ` cv.argcomplete = toDelegate(&enumComplete!UT);`~
2955 ` } else static if (is(UT == bool)) {`~
2956 ` import std.functional : toDelegate;`~
2957 ` cv.argcomplete = toDelegate(&boolComplete!UT);`~
2959 ` cmdlist[aname] = cv;`~
2966 /** register integral console variable with bounded value.
2969 * v = variable symbol
2970 * aminv = minimum value
2971 * amaxv = maximum value
2972 * aname = variable name
2974 * attrs = convar attributes (see `ConVarAttr`)
2976 public ConVarBase
conRegVar(alias v
, T
) (T aminv
, T amaxv
, string aname
, string ahelp
, const(ConVarAttr
)[] attrs
...)
2977 if ((isIntegral
!(typeof(v
)) && isIntegral
!T
) ||
(isFloatingPoint
!(typeof(v
)) && (isIntegral
!T || isFloatingPoint
!T
)))
2979 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp)`);
2982 /** register integral console variable with old/new delegate.
2985 * v = variable symbol
2986 * aname = variable name
2988 * cdg = old/new change delegate
2989 * attrs = convar attributes (see `ConVarAttr`)
2991 ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, void delegate (ConVarBase self
, XUQQ
!(typeof(v
)) oldv
, XUQQ
!(typeof(v
)) newv
) cdg
, const(ConVarAttr
)[] attrs
...)
2992 if (is(XUQQ
!(typeof(v
)) : long) ||
is(XUQQ
!(typeof(v
)) : double) ||
is(XUQQ
!(typeof(v
)) : ConString
) ||
is(XUQQ
!(typeof(v
)) == bool) ||
is(XUQQ
!(typeof(v
)) == enum)) {
2993 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookAfterChangeVV = cdg`);
2996 /** register integral console variable with bounded value and old/new delegate.
2999 * v = variable symbol
3000 * aminv = minimum value
3001 * amaxv = maximum value
3002 * aname = variable name
3004 * cdg = old/new change delegate
3005 * attrs = convar attributes (see `ConVarAttr`)
3007 ConVarBase
conRegVar(alias v
) (XUQQ
!(typeof(v
)) amin
, XUQQ
!(typeof(v
)) amax
, string aname
, string ahelp
, void delegate (ConVarBase self
, XUQQ
!(typeof(v
)) oldv
, XUQQ
!(typeof(v
)) newv
) cdg
, const(ConVarAttr
)[] attrs
...)
3008 if (is(XUQQ
!(typeof(v
)) : long) ||
is(XUQQ
!(typeof(v
)) : double) && !is(XUQQ
!(typeof(v
)) == bool) && !is(XUQQ
!(typeof(v
)) == enum)) {
3009 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, amin, amax, aname, ahelp); cv.hookAfterChangeVV = cdg`);
3012 /** register integral console variable with bounded value.
3015 * v = variable symbol
3016 * aminv = minimum value
3017 * amaxv = maximum value
3018 * aname = variable name
3020 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3021 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3022 * attrs = convar attributes (see `ConVarAttr`)
3024 public ConVarBase
conRegVar(alias v
, T
) (T aminv
, T amaxv
, string aname
, string ahelp
, ConVarBase
.PreChangeHookCB bcb
, ConVarBase
.PostChangeHookCB acb
, const(ConVarAttr
)[] attrs
...) if (isIntegral
!(typeof(v
)) && isIntegral
!T
) {
3025 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3028 /** register integral console variable with bounded value.
3031 * v = variable symbol
3032 * aminv = minimum value
3033 * amaxv = maximum value
3034 * aname = variable name
3036 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3037 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3038 * attrs = convar attributes (see `ConVarAttr`)
3040 public ConVarBase
conRegVar(alias v
, T
) (T aminv
, T amaxv
, string aname
, string ahelp
, ConVarBase
.PostChangeHookCB acb
, ConVarBase
.PreChangeHookCB bcb
, const(ConVarAttr
)[] attrs
...) if (isIntegral
!(typeof(v
)) && isIntegral
!T
) {
3041 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3044 /** register integral console variable with bounded value.
3047 * v = variable symbol
3048 * aminv = minimum value
3049 * amaxv = maximum value
3050 * aname = variable name
3052 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3053 * attrs = convar attributes (see `ConVarAttr`)
3055 public ConVarBase
conRegVar(alias v
, T
) (T aminv
, T amaxv
, string aname
, string ahelp
, ConVarBase
.PreChangeHookCB bcb
, const(ConVarAttr
)[] attrs
...) if (isIntegral
!(typeof(v
)) && isIntegral
!T
) {
3056 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookBeforeChange = bcb`);
3059 /** register integral console variable with bounded value.
3062 * v = variable symbol
3063 * aminv = minimum value
3064 * amaxv = maximum value
3065 * aname = variable name
3067 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3068 * attrs = convar attributes (see `ConVarAttr`)
3070 public ConVarBase
conRegVar(alias v
, T
) (T aminv
, T amaxv
, string aname
, string ahelp
, ConVarBase
.PostChangeHookCB acb
, const(ConVarAttr
)[] attrs
...) if (isIntegral
!(typeof(v
)) && isIntegral
!T
) {
3071 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookAfterChange = acb`);
3075 /** register console variable.
3078 * v = variable symbol
3079 * aname = variable name
3081 * attrs = convar attributes (see `ConVarAttr`)
3083 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3084 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp)`);
3087 /** register console variable.
3090 * v = variable symbol
3091 * aname = variable name
3093 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3094 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3095 * attrs = convar attributes (see `ConVarAttr`)
3097 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, ConVarBase
.PreChangeHookCB bcb
, ConVarBase
.PostChangeHookCB acb
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3098 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3101 /** register console variable.
3104 * v = variable symbol
3105 * aname = variable name
3107 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3108 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3109 * attrs = convar attributes (see `ConVarAttr`)
3111 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, ConVarBase
.PostChangeHookCB acb
, ConVarBase
.PreChangeHookCB bcb
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3112 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3115 /** register console variable.
3118 * v = variable symbol
3119 * aname = variable name
3121 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3122 * attrs = convar attributes (see `ConVarAttr`)
3124 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, ConVarBase
.PreChangeHookCB bcb
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3125 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookBeforeChange = bcb`);
3128 /** register console variable.
3131 * v = variable symbol
3132 * aname = variable name
3134 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3135 * attrs = convar attributes (see `ConVarAttr`)
3137 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, ConVarBase
.PostChangeHookCB acb
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3138 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookAfterChange = acb`);
3142 // ////////////////////////////////////////////////////////////////////////// //
3144 public class ConFuncBase
: ConCommand
{
3145 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); }
3149 /// use `conRegFunc!((ConFuncVA va) {...}, ...)` to switch off automatic argument parsing
3150 public struct ConFuncVA
{
3155 /** register console command.
3159 * aname = variable name
3162 public ConFuncBase
conRegFunc(alias fn
) (string aname
, string ahelp
) if (isCallable
!fn
) {
3163 // we have to make the class nested, so we can use `dg`, which keeps default args
3165 // hack for inline lambdas
3166 static if (is(typeof(&fn
))) {
3172 class ConFunc
: ConFuncBase
{
3173 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); }
3175 override void exec (ConString cmdline
) {
3176 if (checkHelp(cmdline
)) { showHelp
; return; }
3178 static if (args
.length
== 0) {
3179 if (hasArgs(cmdline
)) {
3180 conwriteln("too many args for command '", name
, "'");
3184 } else static if (args
.length
== 1 && is(typeof(args
[0]) == ConFuncVA
)) {
3185 args
[0].cmdline
= cmdline
;
3188 alias defaultArguments
= ParameterDefaults
!fn
;
3189 //pragma(msg, "defs: ", defaultArguments);
3190 import std
.conv
: to
;
3191 ConString
[128] rest
; // to avoid allocations in most cases
3192 ConString
[] restdyn
; // will be used if necessary
3194 foreach (/*auto*/ idx
, ref arg
; args
) {
3195 // populate arguments, with user data if available,
3196 // default if not, and throw if no argument provided
3197 if (hasArgs(cmdline
)) {
3198 import std
.conv
: ConvException
;
3199 static if (is(typeof(arg
) == ConString
[])) {
3201 if (idx
!= args
.length
-1) {
3202 conwriteln("error parsing argument #", xidx
, " for command '", name
, "'");
3205 while (hasArgs(cmdline
)) {
3207 ConString cs
= parseType
!ConString(cmdline
);
3208 if (restpos
== rest
.length
) { restdyn
= rest
[]; ++restpos
; }
3209 if (restpos
< rest
.length
) rest
[restpos
++] = cs
; else restdyn
~= cs
;
3211 } catch (ConvException
) {
3212 conwriteln("error parsing argument #", xidx
, " for command '", name
, "'");
3216 arg
= (restpos
<= rest
.length ? rest
[0..restpos
] : restdyn
);
3219 arg
= parseType
!(typeof(arg
))(cmdline
);
3220 } catch (ConvException
) {
3221 conwriteln("error parsing argument #", idx
+1, " for command '", name
, "'");
3226 static if (!is(defaultArguments
[idx
] == void)) {
3227 arg
= defaultArguments
[idx
];
3229 conwriteln("required argument #", idx
+1, " for command '", name
, "' is missing");
3234 if (hasArgs(cmdline
)) {
3235 conwriteln("too many args for command '", name
, "'");
3238 //static if (is(ReturnType!dg == void))
3244 static if (is(typeof(&fn
))) {
3245 if (aname
.length
== 0) aname
= (&fn
).stringof
[2..$]; // HACK
3247 if (aname
.length
> 0) {
3248 auto cf
= new ConFunc(aname
, ahelp
);
3250 cmdlist
[aname
] = cf
;
3258 // ////////////////////////////////////////////////////////////////////////// //
3259 __gshared
/*ConCommand*/Object
[string
] cmdlist
; // ConCommand or ConAlias
3260 __gshared string
[] cmdlistSorted
;
3263 /** set argument completion delegate for command.
3265 * delegate will be called from `conAddInputChar()`.
3266 * delegate can use `conInputBuffer()` to get current input buffer,
3267 * `conInputBufferCurX()` to get cursor position, and
3268 * `conAddInputChar()` itself to put new chars into buffer.
3270 void conSetArgCompleter (ConString cmdname
, ConCommand
.ArgCompleteCB ac
) {
3271 if (auto cp
= cmdname
in cmdlist
) {
3272 if (auto cmd
= cast(ConCommand
)(*cp
)) cmd
.argcomplete
= ac
;
3277 // ////////////////////////////////////////////////////////////////////////// //
3278 // all following API is thread-unsafe, if the opposite is not written
3279 public bool conHasCommand (ConString name
) { pragma(inline
, true); return ((name
in cmdlist
) !is null); } /// check if console has a command with a given name (thread-unsafe)
3280 public bool conHasAlias (ConString name
) { if (auto cc
= name
in cmdlist
) return (cast(ConAlias
)(*cc
) !is null); else return false; } /// check if console has an alias with a given name (thread-unsafe)
3281 public bool conHasVar (ConString name
) { if (auto cc
= name
in cmdlist
) return (cast(ConVarBase
)(*cc
) !is null); else return false; } /// check if console has a variable with a given name (thread-unsafe)
3283 /// known console commands (funcs and vars) range (thread-unsafe)
3284 /// type: "all", "vars", "funcs"
3285 public auto conByCommand(string type
="all") () if (type
== "all" || type
== "vars" || type
== "funcs" || type
== "aliases" || type
== "archive") {
3286 static struct Range(string type
) {
3291 this (usize stidx
) {
3292 static if (type
== "all") {
3294 } else static if (type
== "vars") {
3295 while (stidx
< cmdlistSorted
.length
&& (cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[stidx
]]) is null) ++stidx
;
3297 } else static if (type
== "funcs") {
3298 while (stidx
< cmdlistSorted
.length
&& (cast(ConFuncBase
)cmdlist
[cmdlistSorted
.ptr
[stidx
]]) is null) ++stidx
;
3300 } else static if (type
== "aliases") {
3301 while (stidx
< cmdlistSorted
.length
&& (cast(ConAlias
)cmdlist
[cmdlistSorted
.ptr
[stidx
]]) is null) ++stidx
;
3303 } else static if (type
== "archive") {
3304 while (stidx
< cmdlistSorted
.length
) {
3305 if (auto v
= cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[stidx
]]) {
3306 if (v
.attrArchive
) break;
3312 static assert(0, "wtf?!");
3317 @property bool empty() () { pragma(inline
, true); return (idx
>= cmdlistSorted
.length
); }
3318 @property string
front() () { pragma(inline
, true); return (idx
< cmdlistSorted
.length ? cmdlistSorted
.ptr
[idx
] : null); }
3319 @property bool frontIsVar() () { pragma(inline
, true); return (idx
< cmdlistSorted
.length ?
(cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]] !is null) : false); }
3320 @property bool frontIsFunc() () { pragma(inline
, true); return (idx
< cmdlistSorted
.length ?
(cast(ConFuncBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]] !is null) : false); }
3321 @property bool frontIsAlias() () { pragma(inline
, true); return (idx
< cmdlistSorted
.length ?
(cast(ConAlias
)cmdlist
[cmdlistSorted
.ptr
[idx
]] !is null) : false); }
3323 if (idx
>= cmdlistSorted
.length
) return;
3324 static if (type
== "all") {
3325 //pragma(inline, true);
3327 } else static if (type
== "vars") {
3329 while (idx
< cmdlistSorted
.length
&& (cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]]) is null) ++idx
;
3330 } else static if (type
== "funcs") {
3332 while (idx
< cmdlistSorted
.length
&& (cast(ConFuncBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]]) is null) ++idx
;
3333 } else static if (type
== "aliases") {
3335 while (idx
< cmdlistSorted
.length
&& (cast(ConAlias
)cmdlist
[cmdlistSorted
.ptr
[idx
]]) is null) ++idx
;
3336 } else static if (type
== "archive") {
3338 while (idx
< cmdlistSorted
.length
) {
3339 if (auto v
= cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]]) {
3340 if (v
.attrArchive
) break;
3345 static assert(0, "wtf?!");
3349 return Range
!type(0);
3353 // ////////////////////////////////////////////////////////////////////////// //
3356 /// get console variable value (thread-safe)
3357 public T
conGetVar(T
=ConString
) (ConString s
) {
3359 scope(exit
) consoleUnlock();
3360 if (auto cc
= s
in cmdlist
) {
3361 if (auto cv
= cast(ConVarBase
)(*cc
)) return cv
.value
!T
;
3367 public T
conGetVar(T
=ConString
) (ConVarBase v
) {
3369 scope(exit
) consoleUnlock();
3370 if (v
!is null) return v
.value
!T
;
3375 /// set console variable value (thread-safe)
3376 public void conSetVar(T
) (ConString s
, T val
) {
3378 scope(exit
) consoleUnlock();
3379 if (auto cc
= s
in cmdlist
) {
3380 if (auto cv
= cast(ConVarBase
)(*cc
)) cv
.value
= val
;
3385 public void conSetVar(T
) (ConVarBase v
, T val
) {
3387 scope(exit
) consoleUnlock();
3388 if (v
!is null) v
.value
= val
;
3392 /// "seal" console variable (i.e. make it read-only) (thread-safe)
3393 public void conSealVar (ConString s
) {
3395 scope(exit
) consoleUnlock();
3396 if (auto cc
= s
in cmdlist
) {
3397 if (auto cv
= cast(ConVarBase
)(*cc
)) cv
.attrReadOnly
= true;
3402 public void conSealVar (ConVarBase v
) {
3404 scope(exit
) consoleUnlock();
3405 if (v
!is null) v
.attrReadOnly
= true;
3409 /// "seal" console variable (i.e. make it r/w) (thread-safe)
3410 public void conUnsealVar (ConString s
) {
3412 scope(exit
) consoleUnlock();
3413 if (auto cc
= s
in cmdlist
) {
3414 if (auto cv
= cast(ConVarBase
)(*cc
)) cv
.attrReadOnly
= false;
3419 public void conUnsealVar (ConVarBase v
) {
3421 scope(exit
) consoleUnlock();
3422 if (v
!is null) v
.attrReadOnly
= false;
3426 // ////////////////////////////////////////////////////////////////////////// //
3429 shared bool ccWaitPrepends
= true;
3430 public @property bool conWaitPrepends () nothrow @trusted @nogc { pragma(inline
, true); import core
.atomic
: atomicLoad
; return atomicLoad(ccWaitPrepends
); } /// shoud "wait" prepend rest of the alias?
3431 public @property void conWaitPrepends (bool v
) nothrow @trusted @nogc { pragma(inline
, true); import core
.atomic
: atomicStore
; atomicStore(ccWaitPrepends
, v
); } /// ditto
3433 __gshared
char* ccWaitPrependStr
= null;
3434 __gshared
uint ccWaitPrependStrSize
, ccWaitPrependStrLen
;
3436 ConString
prependStr () { return ccWaitPrependStr
[0..ccWaitPrependStrLen
]; }
3438 void clearPrependStr () { ccWaitPrependStrLen
= 0; return; }
3440 void setPrependStr (ConString s
) {
3441 if (s
.length
== 0) { ccWaitPrependStrLen
= 0; return; }
3442 if (s
.length
>= int.max
/8) assert(0, "console command too long"); //FIXME
3443 if (s
.length
> ccWaitPrependStrSize
) {
3444 import core
.stdc
.stdlib
: realloc
;
3445 ccWaitPrependStr
= cast(char*)realloc(ccWaitPrependStr
, s
.length
);
3446 if (ccWaitPrependStr
is null) assert(0, "out of memory in cmdcon"); //FIXME
3447 ccWaitPrependStrSize
= cast(uint)s
.length
;
3449 assert(s
.length
<= ccWaitPrependStrSize
);
3450 ccWaitPrependStrLen
= cast(uint)s
.length
;
3451 ccWaitPrependStr
[0..s
.length
] = s
[];
3455 /// execute console command (thread-safe)
3456 /// return `true` if `wait` pseudocommant was hit
3457 public bool conExecute (ConString s
) {
3458 bool waitHit
= false;
3459 auto ss
= s
; // anchor it
3461 scope(exit
) consoleUnlock();
3463 enum MaxAliasExpand
= 256*1024; // 256KB
3465 void conExecuteInternal (ConString s
, int aliassize
, int aliaslevel
) {
3466 ConString anchor
= s
, curs
= s
;
3468 if (aliaslevel
> 0) {
3469 s
= conGetCommandStr(curs
);
3470 if (s
is null) return;
3474 auto w
= ConCommand
.getWord(s
);
3475 if (w
is null) return;
3476 // "wait" pseudocommand
3479 // insert the rest of the code into command queue (at the top),
3480 // so it will be processed on the next `conProcessQueue()` call
3481 while (curs
.length
) {
3482 while (curs
.length
&& curs
.ptr
[0] <= ' ') curs
= curs
[1..$];
3483 if (curs
.length
== 0 || curs
.ptr
[0] != '#') break;
3485 while (curs
.length
&& curs
.ptr
[0] != '\n') curs
= curs
[1..$];
3488 // has something to insert
3489 if (conWaitPrepends
) {
3490 //ccWaitPrependStr = curs;
3491 //concmdPrepend(curs);
3492 setPrependStr(curs
);
3494 concmdAdd
!true(curs
); // ensure new command
3499 version(iv_cmdcon_debug_wait
) conwriteln("CMD: <", w
, ">; args=<", s
, ">; rest=<", curs
, ">");
3500 if (auto cobj
= w
in cmdlist
) {
3501 if (auto cmd
= cast(ConCommand
)(*cobj
)) {
3503 while (s
.length
&& s
.ptr
[0] <= 32) s
= s
[1..$];
3504 //conwriteln("'", s, "'");
3506 scope(exit
) consoleLock();
3508 } else if (auto ali
= cast(ConAlias
)(*cobj
)) {
3510 //TODO: alias arguments
3511 auto atext
= ali
.get
;
3512 //conwriteln("ALIAS: '", w, "': '", atext, "'");
3514 if (aliassize
+atext
.length
> MaxAliasExpand
) throw ConAlias
.exAliasTooDeep
;
3515 if (aliaslevel
>= 128) throw ConAlias
.exAliasTooDeep
;
3516 conExecuteInternal(atext
, aliassize
+cast(int)atext
.length
, aliaslevel
+1);
3519 conwrite("command ");
3520 ConCommand
.writeQuotedString(w
);
3521 conwrite(" is of unknown type (internal error)");
3525 conwrite("command ");
3526 ConCommand
.writeQuotedString(w
);
3527 conwrite(" not found");
3535 conExecuteInternal(s
, 0, 0);
3536 } catch (Exception
) {
3537 conwriteln("error executing console command:\n ", s
);
3543 // ////////////////////////////////////////////////////////////////////////// //
3545 version(contest_func
) unittest {
3546 static void xfunc (int v
, int x
=42) { conwriteln("xfunc: v=", v
, "; x=", x
); }
3548 conRegFunc
!xfunc("", "function with two int args (last has default value '42')");
3549 conExecute("xfunc ?");
3550 conExecute("xfunc 666");
3551 conExecute("xfunc");
3553 conRegFunc
!({conwriteln("!!!");})("bang", "dummy function");
3556 conRegFunc
!((ConFuncVA va
) {
3559 auto w
= ConCommand
.getWord(va
.cmdline
);
3560 if (w
is null) break;
3561 conwriteln("#", idx
, ": [", w
, "]");
3564 })("doo", "another dummy function");
3565 conExecute("doo 1 2 ' 34 '");
3569 // ////////////////////////////////////////////////////////////////////////// //
3570 /** get console commad from array of text.
3572 * console commands are delimited with newlines, but can include various quoted chars and such.
3573 * this function will take care of that, and return something suitable for passing to `conExecute`.
3575 * it will return `null` if there is no command (i.e. end-of-text reached).
3577 public ConString
conGetCommandStr (ref ConString s
) {
3579 while (s
.length
> 0 && s
[0] <= 32) s
= s
[1..$];
3580 if (s
.length
== 0) return null;
3581 if (s
.ptr
[0] != ';') break;
3587 void skipString () {
3588 char qch
= s
.ptr
[pos
++];
3589 while (pos
< s
.length
) {
3590 if (s
.ptr
[pos
] == qch
) { ++pos
; break; }
3591 if (s
.ptr
[pos
++] == '\\') {
3592 if (pos
< s
.length
) {
3593 if (s
.ptr
[pos
] == 'x' || s
.ptr
[pos
] == 'X') pos
+= 2; else ++pos
;
3600 while (pos
< s
.length
) {
3601 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
3603 } else if (s
.ptr
[pos
++] == '\n') {
3609 if (s
.ptr
[0] == '#') {
3611 if (pos
>= s
.length
) { s
= s
[$..$]; return null; }
3616 while (pos
< s
.length
) {
3617 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
3619 } else if (s
.ptr
[pos
] == ';' || s
.ptr
[pos
] == '#' || s
.ptr
[pos
] == '\n') {
3620 auto res
= s
[0..pos
];
3621 if (s
.ptr
[pos
] == '#') s
= s
[pos
..$]; else s
= s
[pos
+1..$];
3633 version(contest_cpx
) unittest {
3634 ConString s
= "boo; woo \";\" 42#cmt\ntest\nfoo";
3636 auto c
= conGetCommandStr(s
);
3637 conwriteln("[", c
, "] : [", s
, "]");
3640 auto c
= conGetCommandStr(s
);
3641 conwriteln("[", c
, "] : [", s
, "]");
3644 auto c
= conGetCommandStr(s
);
3645 conwriteln("[", c
, "] : [", s
, "]");
3648 auto c
= conGetCommandStr(s
);
3649 conwriteln("[", c
, "] : [", s
, "]");
3654 // ////////////////////////////////////////////////////////////////////////// //
3655 // console always has "echo" command, no need to register it.
3656 public class ConCommandEcho
: ConCommand
{
3657 this () { super("echo", "write string to console"); }
3659 override void exec (ConString cmdline
) {
3660 if (checkHelp(cmdline
)) { showHelp
; return; }
3661 if (!hasArgs(cmdline
)) { conwriteln
; return; }
3662 bool needSpace
= false;
3664 auto w
= getWord(cmdline
);
3665 if (w
is null) break;
3666 if (needSpace
) conwrite(" "); else needSpace
= true;
3669 while (pos
< w
.length
&& w
.ptr
[pos
] != '$') ++pos
;
3670 if (w
.length
-pos
> 1 && w
.ptr
[pos
+1] == '$') {
3671 conwrite(w
[0..pos
+1]);
3673 } else if (w
.length
-pos
<= 1) {
3679 if (pos
> 0) conwrite(w
[0..pos
]);
3681 if (w
.ptr
[pos
] == '{') {
3684 while (pos
< w
.length
&& w
.ptr
[pos
] != '}') ++pos
;
3686 if (pos
< w
.length
) ++pos
;
3691 while (pos
< w
.length
) {
3692 char ch
= w
.ptr
[pos
];
3693 if (ch
== '_' ||
(ch
>= '0' && ch
<= '9') ||
(ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z')) {
3703 if (auto cc
= vname
in cmdlist
) {
3704 if (auto cv
= cast(ConVarBase
)(*cc
)) {
3726 // ////////////////////////////////////////////////////////////////////////// //
3727 // console always has "alias" command, no need to register it.
3728 public class ConCommandAlias
: ConCommand
{
3729 this () { super("alias", "set or remove alias"); }
3731 override void exec (ConString cmdline
) {
3732 if (checkHelp(cmdline
)) { showHelp
; return; }
3733 if (!hasArgs(cmdline
)) { conwriteln("alias what?"); return; }
3735 auto name
= getWord(cmdline
);
3736 if (name
is null || name
.length
== 0) { conwriteln("alias what?"); return; }
3737 if (name
.length
> 128) { conwriteln("alias name too long: ", name
); return; }
3739 char[129] nbuf
= void;
3740 nbuf
[0..name
.length
] = name
;
3741 const(char)[] xname
= nbuf
[0..name
.length
];
3744 auto v
= getWord(cmdline
);
3745 while (v
.length
&& v
[0] <= ' ') v
= v
[1..$];
3746 while (v
.length
&& v
[$-1] <= ' ') v
= v
[0..$-1];
3748 if (hasArgs(cmdline
)) { conwriteln("too many arguments to `alias` command"); return; }
3750 if (auto cp
= xname
in cmdlist
) {
3752 auto ali
= cast(ConAlias
)(*cp
);
3753 if (ali
is null) { conwriteln("can't change existing command/var to alias: ", xname
); return; }
3754 if (v
.length
== 0) {
3756 cmdlist
.remove(cast(string
)xname
); // it is safe to cast here
3757 foreach (immutable idx
, string n
; cmdlistSorted
) {
3759 foreach (immutable c
; idx
+1..cmdlistSorted
.length
) cmdlistSorted
[c
-1] = cmdlistSorted
[c
];
3770 auto ali
= new ConAlias(v
);
3771 //conwriteln(xname, ": <", ali.get, ">");
3772 string sname
= xname
.idup
;
3774 cmdlist
[sname
] = ali
;
3780 // ////////////////////////////////////////////////////////////////////////// //
3781 /// create "user console variable"
3782 ConVarBase
conRegUserVar(T
) (string aname
, string help
, const(ConVarAttr
)[] attrs
...) {
3783 if (aname
.length
== 0) return null;
3785 static if (is(T
: long) ||
is(T
: double)) {
3787 v
= new ConVar
!T(var
, aname
, help
);
3788 } else static if (is(T
: const(char)[])) {
3791 v
= new ConVar
!T(&var
[0], aname
, help
);
3793 static assert(0, "can't create uservar of type '"~T
.stringof
~"'");
3796 v
.mAttrs |
= ConVarAttr
.User
;
3803 /// create bounded "user console variable"
3804 ConVarBase
conRegUserVar(T
) (T amin
, T amax
, string aname
, string help
, const(ConVarAttr
)[] attrs
...) if (is(T
: long) ||
is(T
: double)) {
3805 if (aname
.length
== 0) return null;
3807 static if (is(T
: long) ||
is(T
: double)) {
3809 v
= new ConVar
!T(var
, amin
, amax
, aname
, help
);
3810 } else static if (is(T
: const(char)[])) {
3813 v
= new ConVar
!T(&var
[0], aname
, help
);
3815 static assert(0, "can't create uservar of type '"~T
.stringof
~"'");
3818 v
.mAttrs |
= ConVarAttr
.User
;
3825 // ////////////////////////////////////////////////////////////////////////// //
3826 // console always has "uvar_create" command, no need to register it.
3827 public class ConCommandUserConVar
: ConCommand
{
3828 this () { super("uvar_create", "create user convar: userconvar \"<type> <name>\"; type is <int|str|bool|float|double>"); }
3830 override void exec (ConString cmdline
) {
3831 if (checkHelp(cmdline
)) { showHelp
; return; }
3832 if (!hasArgs(cmdline
)) return;
3833 auto type
= getWord(cmdline
);
3834 auto name
= getWord(cmdline
);
3835 if (type
.length
== 0 || name
.length
== 0) return;
3836 if (name
in cmdlist
) { conwriteln("console variable '", name
, "' already exists"); return; }
3838 string aname
= name
.idup
;
3842 v
= new ConVar
!int(var
, aname
, null);
3845 //string* var = new string; // alas
3848 v
= new ConVar
!string(&var
[0], aname
, null);
3851 bool* var
= new bool;
3852 v
= new ConVar
!bool(var
, aname
, null);
3855 float* var
= new float;
3856 v
= new ConVar
!float(var
, aname
, null);
3859 double* var
= new double;
3860 v
= new ConVar
!double(var
, aname
, null);
3863 conwriteln("can't create console variable '", name
, "' of unknown type '", type
, "'");
3866 v
.setAttrs
!true(ConVarAttr
.User
);
3873 // ////////////////////////////////////////////////////////////////////////// //
3874 // console always has "uvar_remove" command, no need to register it.
3875 public class ConCommandKillUserConVar
: ConCommand
{
3876 this () { super("uvar_remove", "remove user convar: killuserconvar \"<name>\""); }
3878 override void exec (ConString cmdline
) {
3879 if (checkHelp(cmdline
)) { showHelp
; return; }
3880 if (!hasArgs(cmdline
)) return;
3881 auto name
= getWord(cmdline
);
3882 if (name
.length
== 0) return;
3883 if (auto vp
= name
in cmdlist
) {
3884 if (auto var
= cast(ConVarBase
)(*vp
)) {
3885 if (!var
.attrUser
) {
3886 conwriteln("console command '", name
, "' is not a uservar");
3890 cmdlist
.remove(cast(string
)name
); // it is safe to cast here
3891 foreach (immutable idx
, string n
; cmdlistSorted
) {
3893 foreach (immutable c
; idx
+1..cmdlistSorted
.length
) cmdlistSorted
[c
-1] = cmdlistSorted
[c
];
3898 conwriteln("console command '", name
, "' is not a var");
3902 conwriteln("console variable '", name
, "' doesn't exist");
3909 shared static this () {
3911 cmdlist
["echo"] = new ConCommandEcho();
3913 cmdlist
["alias"] = new ConCommandAlias();
3914 addName("uvar_create");
3915 cmdlist
["uvar_create"] = new ConCommandUserConVar();
3916 addName("uvar_remove");
3917 cmdlist
["uvar_remove"] = new ConCommandKillUserConVar();
3921 /** replace "$var" in string.
3923 * this function will replace "$var" in string with console var values.
3926 * dest = destination buffer
3929 public char[] conFormatStr (char[] dest
, ConString s
) {
3932 void put (ConString ss
) {
3933 if (ss
.length
== 0) return;
3934 auto len
= ss
.length
;
3935 if (dest
.length
-dpos
< len
) len
= dest
.length
-dpos
;
3937 dest
[dpos
..dpos
+len
] = ss
[];
3944 while (pos
< s
.length
&& s
.ptr
[pos
] != '$') ++pos
;
3945 if (s
.length
-pos
> 1 && s
.ptr
[pos
+1] == '$') {
3948 } else if (s
.length
-pos
<= 1) {
3954 if (pos
> 0) put(s
[0..pos
]);
3956 if (s
.ptr
[pos
] == '{') {
3959 while (pos
< s
.length
&& s
.ptr
[pos
] != '}') ++pos
;
3961 if (pos
< s
.length
) ++pos
;
3966 while (pos
< s
.length
) {
3967 char ch
= s
.ptr
[pos
];
3968 if (ch
== '_' ||
(ch
>= '0' && ch
<= '9') ||
(ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z')) {
3978 if (auto cc
= vname
in cmdlist
) {
3979 if (auto cv
= cast(ConVarBase
)(*cc
)) {
3995 return dest
[0..dpos
];
4000 version(contest_echo
) unittest {
4001 __gshared
int vi
= 42;
4002 __gshared string vs
= "str";
4003 __gshared
bool vb
= true;
4004 conRegVar
!vi("vi", "int var");
4005 conRegVar
!vs("vs", "string var");
4006 conRegVar
!vb("vb", "bool var");
4007 conRegVar
!vb("r_interpolation", "bool var");
4008 conwriteln("=================");
4009 conExecute("r_interpolation");
4010 conExecute("echo ?");
4011 conExecute("echo vs=$vs, vi=${vi}, vb=${vb}!");
4014 auto s
= buf
.conFormatStr("vs=$vs, vi=${vi}, vb=${vb}!");
4015 conwriteln("[", s
, "]");
4016 foreach (/*auto*/ kv
; cmdlist
.byKeyValue
) conwriteln(" ", kv
.key
);
4017 assert("r_interpolation" in cmdlist
);
4018 s
= buf
.conFormatStr("Interpolation: $r_interpolation");
4019 conwriteln("[", s
, "]");
4021 conwriteln("vi=", conGetVar
!int("vi"));
4022 conwriteln("vi=", conGetVar("vi"));
4023 conwriteln("vs=", conGetVar("vi"));
4024 conwriteln("vb=", conGetVar
!int("vb"));
4025 conwriteln("vb=", conGetVar
!bool("vb"));
4026 conwriteln("vb=", conGetVar("vb"));
4031 version(contest_cmdlist
) unittest {
4033 auto cl
= conByCommand
;
4034 conwriteln("=== all ===");
4036 if (cl
.frontIsVar
) conwrite("VAR ");
4037 else if (cl
.frontIsFunc
) conwrite("FUNC ");
4038 else conwrite("UNK ");
4039 conwriteln("[", cl
.front
, "]");
4044 conwriteln("=== funcs ===");
4045 foreach (/*auto*/ clx
; conByCommand
!"funcs") conwriteln(" [", clx
, "]");
4047 conwriteln("=== vars ===");
4048 foreach (/*auto*/ clx
; conByCommand
!"vars") conwriteln(" [", clx
, "]");
4052 shared static this () {
4055 foreach (string name
; cmdlistSorted
[]) {
4056 if (auto ccp
= name
in cmdlist
) {
4057 if (name
.length
> 64) {
4058 if (maxlen
< 64) maxlen
= 64;
4060 if (name
.length
> maxlen
) maxlen
= cast(uint)name
.length
;
4064 foreach (string name
; cmdlistSorted
[]) {
4065 if (auto ccp
= name
in cmdlist
) {
4067 foreach (immutable _
; name
.length
..maxlen
) conwrite(" ");
4068 if (auto cmd
= cast(ConCommand
)(*ccp
)) {
4069 conwriteln(" -- ", cmd
.help
);
4071 conwriteln(" -- alias");
4075 })("cmdlist", "list all known commands and variables");
4079 // ////////////////////////////////////////////////////////////////////////// //
4080 // simple input buffer for console
4082 __gshared
char[4096] concli
= 0;
4083 __gshared
uint conclilen
= 0;
4084 __gshared
int concurx
= 0;
4085 public __gshared
void delegate () nothrow @trusted conInputChangedCB
; /// can be called in any thread
4087 shared uint inchangeCount
= 1;
4088 public @property uint conInputLastChange () nothrow @trusted @nogc { pragma(inline
, true); import core
.atomic
; return atomicLoad(inchangeCount
); } /// changed when something was put to console input buffer (thread-safe)
4089 public void conInputIncLastChange () nothrow @trusted { pragma(inline
, true); import core
.atomic
; atomicOp
!"+="(inchangeCount
, 1); if (conInputChangedCB
!is null) conInputChangedCB(); } /// increment console input buffer change flag (thread-safe)
4091 public @property ConString
conInputBuffer() () @trusted { pragma(inline
, true); return concli
[0..conclilen
]; } /// returns console input buffer (not thread-safe)
4092 public @property int conInputBufferCurX() () @trusted { pragma(inline
, true); return concurx
; } /// returns cursor position in input buffer: [0..conclilen] (not thread-safe)
4095 /** clear console input buffer. (not thread-safe)
4097 * call this function with `addToHistory:true` to add current input buffer to history.
4098 * it is safe to call this for empty input buffer, history won't get empty line.
4101 * addToHistory = true if current buffer should be "added to history", so current history index should be reset and history will be updated
4103 public void conInputBufferClear() (bool addToHistory
=false) @trusted {
4104 if (conclilen
> 0) {
4105 if (addToHistory
) conHistoryAdd(conInputBuffer
);
4108 conInputIncLastChange();
4110 if (addToHistory
) conhisidx
= -1;
4114 private struct ConHistItem
{
4115 char* dbuf
; // without trailing 0
4116 char[256] sbuf
= 0; // static buffer
4117 uint len
; // of dbuf or sbuf, without trailing 0
4118 uint alloted
; // !0: `dbuf` is used
4119 nothrow @trusted @nogc:
4120 void releaseMemory () { import core
.stdc
.stdlib
: free
; if (dbuf
!is null) free(dbuf
); dbuf
= null; alloted
= len
= 0; }
4121 @property const(char)[] str () { pragma(inline
, true); return (alloted ? dbuf
: sbuf
.ptr
)[0..len
]; }
4122 @property void str (const(char)[] s
) {
4123 import core
.stdc
.stdlib
: malloc
, realloc
;
4124 if (s
.length
> 65536) s
= s
[0..65536]; // just in case
4125 if (s
.length
== 0) { len
= 0; return; }
4126 // try to put in allocated space
4128 if (s
.length
> alloted
) {
4129 auto np
= cast(char*)realloc(dbuf
, s
.length
);
4131 // can't allocate memory
4132 dbuf
[0..alloted
] = s
[0..alloted
];
4136 alloted
= cast(uint)s
.length
;
4139 dbuf
[0..s
.length
] = s
[];
4140 len
= cast(uint)s
.length
;
4143 // fit in static buf?
4144 if (s
.length
<= sbuf
.length
) {
4145 sbuf
.ptr
[0..s
.length
] = s
[];
4146 len
= cast(uint)s
.length
;
4149 // allocate dynamic buffer
4150 dbuf
= cast(char*)malloc(s
.length
);
4152 // alas; trim and use static one
4153 sbuf
[] = s
[0..sbuf
.length
];
4154 len
= cast(uint)sbuf
.length
;
4156 // ok, use dynamic buffer
4157 alloted
= len
= cast(uint)s
.length
;
4164 //TODO: make circular buffer
4165 private enum ConHistMax
= 8192;
4166 __gshared ConHistItem
* concmdhistory
= null;
4167 __gshared
int conhisidx
= -1;
4168 __gshared
uint conhismax
= 128;
4169 __gshared
uint conhisused
= 0;
4170 __gshared
uint conhisalloted
= 0;
4173 shared static this () {
4174 conRegVar
!conhismax(1, ConHistMax
, "r_conhistmax", "maximum commands in console input history");
4178 // free unused slots if `conhismax` was changed
4179 private void conHistShrinkBuf () {
4180 import core
.stdc
.stdlib
: realloc
;
4181 import core
.stdc
.string
: memmove
, memset
;
4182 if (conhisalloted
<= conhismax
) return;
4183 auto tokill
= conhisalloted
-conhismax
;
4184 debug(concmd_history
) conwriteln("removing ", tokill
, " items out of ", conhisalloted
);
4185 // discard old items
4186 if (conhisused
> conhismax
) {
4187 auto todis
= conhisused
-conhismax
;
4188 debug(concmd_history
) conwriteln("discarding ", todis
, " items out of ", conhisused
);
4190 foreach (ref ConHistItem it
; concmdhistory
[0..todis
]) it
.releaseMemory();
4191 // move array elements
4192 memmove(concmdhistory
, concmdhistory
+todis
, ConHistItem
.sizeof
*conhismax
);
4193 // clear what is left
4194 memset(concmdhistory
+conhismax
, 0, ConHistItem
.sizeof
*todis
);
4195 conhisused
= conhismax
;
4198 auto np
= cast(ConHistItem
*)realloc(concmdhistory
, ConHistItem
.sizeof
*conhismax
);
4199 if (np
!is null) concmdhistory
= np
;
4200 conhisalloted
= conhismax
;
4204 // allocate space for new command, return it's index or -1 on error
4205 private int conHistAllot () {
4206 import core
.stdc
.stdlib
: realloc
;
4207 import core
.stdc
.string
: memmove
, memset
;
4208 conHistShrinkBuf(); // shrink buffer, if necessary
4209 if (conhisused
>= conhisalloted
&& conhisused
< conhismax
) {
4211 uint newsz
= conhisalloted
+64;
4212 if (newsz
> conhismax
) newsz
= conhismax
;
4213 debug(concmd_history
) conwriteln("adding ", newsz
-conhisalloted
, " items (now: ", conhisalloted
, ")");
4214 auto np
= cast(ConHistItem
*)realloc(concmdhistory
, ConHistItem
.sizeof
*newsz
);
4219 memset(concmdhistory
+conhisalloted
, 0, ConHistItem
.sizeof
*(newsz
-conhisalloted
));
4220 conhisalloted
= newsz
; // fix it! ;-)
4221 return conhisused
++;
4223 // alas, have to move
4225 if (conhisalloted
== 0) return -1; // no memory
4226 if (conhisalloted
== 1) { conhisused
= 1; return 0; } // always
4227 assert(conhisused
<= conhisalloted
);
4228 if (conhisused
== conhisalloted
) {
4229 ConHistItem tmp
= concmdhistory
[0];
4232 memmove(concmdhistory
, concmdhistory
+1, ConHistItem
.sizeof
*conhisused
);
4233 //memset(concmdhistory+conhisused, 0, ConHistItem.sizeof);
4234 memmove(concmdhistory
+conhisused
, &tmp
, ConHistItem
.sizeof
);
4236 return conhisused
++;
4240 /// returns input buffer history item (0 is oldest) or null if there are no more items (not thread-safe)
4241 public ConString
conHistoryAt (int idx
) {
4242 if (idx
< 0 || idx
>= conhisused
) return null;
4243 return concmdhistory
[conhisused
-idx
-1].str;
4247 /// find command in input history, return index or -1 (not thread-safe)
4248 public int conHistoryFind (ConString cmd
) {
4249 while (cmd
.length
&& cmd
[0] <= 32) cmd
= cmd
[1..$];
4250 while (cmd
.length
&& cmd
[$-1] <= 32) cmd
= cmd
[0..$-1];
4251 if (cmd
.length
== 0) return -1;
4252 foreach (int idx
; 0..conhisused
) {
4253 auto c
= concmdhistory
[idx
].str;
4254 while (c
.length
> 0 && c
[0] <= 32) c
= c
[1..$];
4255 while (c
.length
> 0 && c
[$-1] <= 32) c
= c
[0..$-1];
4256 if (c
== cmd
) return conhisused
-idx
-1;
4262 /// add command to history. will take care about duplicate commands. (not thread-safe)
4263 public void conHistoryAdd (ConString cmd
) {
4264 import core
.stdc
.string
: memmove
, memset
;
4266 while (cmd
.length
&& cmd
[0] <= 32) cmd
= cmd
[1..$];
4267 while (cmd
.length
&& cmd
[$-1] <= 32) cmd
= cmd
[0..$-1];
4268 if (cmd
.length
== 0) return;
4269 auto idx
= conHistoryFind(cmd
);
4271 debug(concmd_history
) conwriteln("command found! idx=", idx
, "; real idx=", conhisused
-idx
-1, "; used=", conhisused
);
4272 idx
= conhisused
-idx
-1; // fix index
4273 // move command to bottom
4274 if (idx
== conhisused
-1) return; // nothing to do
4276 ConHistItem tmp
= concmdhistory
[idx
];
4278 memmove(concmdhistory
+idx
, concmdhistory
+idx
+1, ConHistItem
.sizeof
*(conhisused
-idx
-1));
4279 //memset(concmdhistory+conhisused, 0, ConHistItem.sizeof);
4280 memmove(concmdhistory
+conhisused
-1, &tmp
, ConHistItem
.sizeof
);
4283 idx
= conHistAllot();
4284 if (idx
< 0) return; // alas
4285 concmdhistory
[idx
].str = orgcmd
;
4290 /// special characters for `conAddInputChar()`
4291 public enum ConInputChar
: char {
4298 PageUp
= '\x07', ///
4299 Backspace
= '\x08', ///
4302 PageDown
= '\x0b', ///
4303 Delete
= '\x0c', ///
4305 Insert
= '\x0e', ///
4308 LineUp
= '\x1a', ///
4309 LineDown
= '\x1b', ///
4314 /// process console input char (won't execute commands, but will do autocompletion and history) (not thread-safe)
4315 public void conAddInputChar (char ch
) {
4316 __gshared
int prevWasEmptyAndTab
= 0;
4318 bool insChars (const(char)[] s
...) {
4319 if (s
.length
== 0) return false;
4320 if (concli
.length
-conclilen
< s
.length
) return false;
4321 foreach (char ch
; s
) {
4322 if (concurx
== conclilen
) {
4323 concli
.ptr
[conclilen
++] = ch
;
4326 import core
.stdc
.string
: memmove
;
4327 memmove(concli
.ptr
+concurx
+1, concli
.ptr
+concurx
, conclilen
-concurx
);
4328 concli
.ptr
[concurx
++] = ch
;
4336 if (ch
== ConInputChar
.Tab
) {
4338 if (++prevWasEmptyAndTab
< 2) return;
4340 prevWasEmptyAndTab
= 0;
4343 // if there are space(s) before cursor position, this is argument completion
4344 bool doArgAC
= false;
4347 while (p
> 0 && concli
.ptr
[p
-1] > ' ') --p
;
4351 prevWasEmptyAndTab
= 0;
4352 // yeah, arguments; first, get command name
4354 while (stp
< concurx
&& concli
.ptr
[stp
] <= ' ') ++stp
;
4355 if (stp
>= concurx
) return; // alas
4357 while (ste
< concurx
&& concli
.ptr
[ste
] > ' ') ++ste
;
4358 if (auto cp
= concli
[stp
..ste
] in cmdlist
) {
4359 if (auto cmd
= cast(ConCommand
)(*cp
)) {
4360 if (cmd
.argcomplete
!is null) try { cmd
.argcomplete(cmd
); } catch (Exception
) {} // sorry
4365 string minPfx
= null;
4366 // find longest command
4367 foreach (/*auto*/ name
; conByCommand
) {
4368 if (name
.length
>= concurx
&& name
.length
> minPfx
.length
&& name
[0..concurx
] == concli
[0..concurx
]) minPfx
= name
;
4370 string longestCmd
= minPfx
;
4371 //conwriteln("longest command: [", minPfx, "]");
4372 // find longest prefix
4373 foreach (/*auto*/ name
; conByCommand
) {
4374 if (name
.length
< concurx
) continue;
4375 if (name
[0..concurx
] != concli
[0..concurx
]) continue;
4377 while (pos
< name
.length
&& pos
< minPfx
.length
&& minPfx
.ptr
[pos
] == name
.ptr
[pos
]) ++pos
;
4378 if (pos
< minPfx
.length
) minPfx
= minPfx
[0..pos
];
4380 if (minPfx
.length
> concli
.length
) minPfx
= minPfx
[0..concli
.length
];
4381 //conwriteln("longest prefix : [", minPfx, "]");
4382 if (minPfx
.length
>= concurx
) {
4383 // wow! has something to add
4384 bool doRet
= (minPfx
.length
> concurx
);
4385 if (insChars(minPfx
[concurx
..$])) {
4386 // insert space after complete command
4387 if (longestCmd
.length
== minPfx
.length
&& conHasCommand(minPfx
)) {
4388 if (concurx
>= conclilen || concli
.ptr
[concurx
] > ' ') {
4389 doRet
= insChars(' ');
4395 conInputIncLastChange();
4400 // nope, print all available commands
4401 bool needDelimiter
= true;
4402 foreach (/*auto*/ name
; conByCommand
) {
4403 if (name
.length
== 0) continue;
4405 if (name
.length
< concurx
) continue;
4406 if (name
[0..concurx
] != concli
[0..concurx
]) continue;
4408 // skip "unusal" commands"
4410 if (!((ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z') || ch
== '_')) continue;
4412 if (needDelimiter
) { conwriteln("----------------"); needDelimiter
= false; }
4417 // process other keys
4418 prevWasEmptyAndTab
= 0;
4420 if (ch
== ConInputChar
.Backspace
) {
4422 if (concurx
< conclilen
) {
4423 import core
.stdc
.string
: memmove
;
4424 memmove(concli
.ptr
+concurx
-1, concli
.ptr
+concurx
, conclilen
-concurx
);
4428 conInputIncLastChange();
4433 if (ch
== ConInputChar
.Delete
) {
4434 if (concurx
< conclilen
) {
4435 if (conclilen
> 1) {
4436 import core
.stdc
.string
: memmove
;
4437 memmove(concli
.ptr
+concurx
, concli
.ptr
+concurx
+1, conclilen
-concurx
-1);
4440 conInputIncLastChange();
4444 // ^W (delete previous word)
4445 if (ch
== ConInputChar
.CtrlW
) {
4449 while (stp
> 0 && concli
.ptr
[stp
-1] <= ' ') --stp
;
4451 while (stp
> 0 && concli
.ptr
[stp
-1] > ' ') --stp
;
4452 if (stp
< concurx
) {
4453 import core
.stdc
.string
: memmove
;
4454 // delete from stp to concurx
4455 int dlen
= concurx
-stp
;
4456 assert(concurx
<= conclilen
);
4457 if (concurx
< conclilen
) memmove(concli
.ptr
+stp
, concli
.ptr
+concurx
, conclilen
-concurx
);
4460 conInputIncLastChange();
4466 if (ch
== ConInputChar
.CtrlY
) {
4467 if (conclilen
> 0) { conclilen
= 0; concurx
= 0; conInputIncLastChange(); }
4471 if (ch
== ConInputChar
.Home
) {
4474 conInputIncLastChange();
4479 if (ch
== ConInputChar
.End
) {
4480 if (concurx
< conclilen
) {
4481 concurx
= conclilen
;
4482 conInputIncLastChange();
4487 if (ch
== ConInputChar
.Up
) {
4489 auto cmd
= conHistoryAt(conhisidx
);
4490 if (cmd
.length
== 0) {
4493 concli
[0..cmd
.length
] = cmd
[];
4494 conclilen
= cast(uint)cmd
.length
;
4495 concurx
= conclilen
;
4496 conInputIncLastChange();
4501 if (ch
== ConInputChar
.Down
) {
4503 auto cmd
= conHistoryAt(conhisidx
);
4504 if (cmd
.length
== 0 && conhisidx
< -1) {
4507 concli
[0..cmd
.length
] = cmd
[];
4508 conclilen
= cast(uint)cmd
.length
;
4509 concurx
= conclilen
;
4510 conInputIncLastChange();
4515 if (ch
== ConInputChar
.Left
) {
4518 conInputIncLastChange();
4523 if (ch
== ConInputChar
.Right
) {
4524 if (concurx
< conclilen
) {
4526 conInputIncLastChange();
4531 if (ch
< ' ' || ch
> 127) return;
4532 if (insChars(ch
)) conInputIncLastChange();
4536 // ////////////////////////////////////////////////////////////////////////// //
4539 /// add console command to execution queue (thread-safe)
4540 public void concmd (ConString cmd
) {
4542 scope(exit
) { consoleUnlock(); conInputIncLastChange(); }
4547 /// add console command to execution queue (thread-safe)
4548 /// this understands '%s' and '%q' (quoted string, but without surroinding quotes)
4549 /// it also understands '$var', '${var}', '${var:ifabsent}', '${var?ifempty}'
4550 /// var substitution in quotes will be automatically quoted
4551 /// string substitution in quotes will be automatically quoted
4552 public void concmdf(string fmt
, A
...) (A args
) { concmdfdg(fmt
, null, args
); }
4555 /// add console command to execution queue (thread-safe)
4556 /// this understands '%s' and '%q' (quoted string, but without surroinding quotes)
4557 /// it also understands '$var', '${var}', '${var:ifabsent}', '${var?ifempty}'
4558 /// var substitution in quotes will be automatically quoted
4559 /// string substitution in quotes will be automatically quoted
4560 public void concmdfdg(A
...) (ConString fmt
, scope void delegate (ConString cmd
) cmddg
, A args
) {
4562 scope(exit
) { consoleUnlock(); conInputIncLastChange(); }
4564 auto fmtanchor
= fmt
;
4566 bool ensureCmd
= true;
4569 usize stpos
= usize
.max
;
4571 void puts(bool quote
=false) (const(char)[] s
...) {
4573 if (ensureCmd
) { concmdEnsureNewCommand(); ensureCmd
= false; }
4574 if (stpos
== usize
.max
) stpos
= concmdbufpos
;
4577 foreach (immutable idx
, char ch
; s
) {
4578 if (ch
< ' ' || ch
== 127 || ch
== '#' || ch
== '"' || ch
== '\\') {
4579 import core
.stdc
.stdio
: snprintf
;
4580 auto len
= snprintf(buf
.ptr
, buf
.length
, "\\x%02x", cast(uint)ch
);
4581 concmdAdd
!false(buf
[0..len
]);
4583 concmdAdd
!false(s
[idx
..idx
+1]);
4592 void putint(TT
) (TT nn
) {
4595 static if (is(TT
== shared)) {
4598 T n
= atomicLoad(nn
);
4604 static if (is(T
== long)) {
4605 if (n
== 0x8000_0000_0000_0000uL) { puts("-9223372036854775808"); return; }
4606 } else static if (is(T
== int)) {
4607 if (n
== 0x8000_0000u) { puts("-2147483648"); return; }
4608 } else static if (is(T
== short)) {
4609 if ((n
&0xffff) == 0x8000u
) { puts("-32768"); return; }
4610 } else static if (is(T
== byte)) {
4611 if ((n
&0xff) == 0x80u
) { puts("-128"); return; }
4614 static if (__traits(isUnsigned
, T
)) {
4621 int bpos
= buf
.length
;
4623 //if (bpos == 0) assert(0, "internal printer error");
4624 buf
.ptr
[--bpos
] = cast(char)('0'+n
%10);
4628 //if (bpos == 0) assert(0, "internal printer error");
4629 buf
.ptr
[--bpos
] = '-';
4634 void putfloat(TT
) (TT nn
) {
4635 import core
.stdc
.stdlib
: malloc
, realloc
;
4637 static if (is(TT
== shared)) {
4640 T n
= atomicLoad(nn
);
4647 static usize buflen
= 0;
4651 buf
= cast(char*)malloc(buflen
);
4652 if (buf
is null) assert(0, "out of memory");
4656 import core
.stdc
.stdio
: snprintf
;
4657 auto plen
= snprintf(buf
, buflen
, "%g", cast(double)n
);
4658 if (plen
>= buflen
) {
4660 buf
= cast(char*)realloc(buf
, buflen
);
4661 if (buf
is null) assert(0, "out of memory");
4669 static bool isGoodIdChar (char ch
) pure nothrow @safe @nogc {
4670 pragma(inline
, true);
4672 (ch
>= '0' && ch
<= '9') ||
4673 (ch
>= 'A' && ch
<= 'Z') ||
4674 (ch
>= 'a' && ch
<= 'z') ||
4678 void processUntilFSp () {
4679 while (pos
< fmt
.length
) {
4681 while (epos
< fmt
.length
&& fmt
[epos
] != '%') {
4682 if (fmt
[epos
] == '\\' && fmt
.length
-epos
> 1 /*&& (fmt[epos+1] == '"' || fmt[epos+1] == '$')*/) {
4683 puts(fmt
[pos
..epos
]);
4688 if (inQ
&& fmt
[epos
] == inQ
) inQ
= 0;
4689 if (fmt
[epos
] == '"') {
4691 } else if (fmt
[epos
] == '$') {
4692 puts(fmt
[pos
..epos
]);
4694 if (fmt
.length
-epos
> 0 && fmt
[epos
+1] != '{') {
4697 while (epos
< fmt
.length
&& isGoodIdChar(fmt
[epos
])) ++epos
;
4699 if (auto cc
= fmt
[pos
+1..epos
] in cmdlist
) {
4700 if (auto cv
= cast(ConVarBase
)(*cc
)) {
4701 if (inQ
) puts
!true(cv
.strval
); else puts
!false(cv
.strval
);
4711 //FIXME: allow escaping of '}'
4712 while (epos
< fmt
.length
&& fmt
[epos
] != '}') ++epos
;
4713 if (epos
>= fmt
.length
) { epos
= pos
+1; continue; } // no '}'
4716 while (cpos
< epos
&& fmt
[cpos
] != ':' && fmt
[cpos
] != '?') ++cpos
;
4717 if (auto cc
= fmt
[pos
..cpos
] in cmdlist
) {
4718 if (auto cv
= cast(ConVarBase
)(*cc
)) {
4719 auto sv
= cv
.strval
;
4720 // process replacement value for empty strings
4721 if (cpos
< epos
&& fmt
[cpos
] == '?' && sv
.length
== 0) {
4722 if (inQ
) puts
!true(fmt
[cpos
+1..epos
]); else puts
!false(fmt
[cpos
+1..epos
]);
4724 if (inQ
) puts
!true(sv
); else puts
!false(sv
);
4730 // either no such command, or it's not a var
4731 if (cpos
< epos
&& (fmt
[cpos
] == '?' || fmt
[cpos
] == ':')) {
4732 if (inQ
) puts
!true(fmt
[cpos
+1..epos
]); else puts
!false(fmt
[cpos
+1..epos
]);
4741 puts(fmt
[pos
..epos
]);
4743 if (pos
>= fmt
.length
) break;
4745 if (fmt
.length
-pos
< 2 || fmt
[pos
+1] == '%') { puts('%'); pos
+= 2; continue; }
4750 foreach (immutable argnum
, /*auto*/ att
; A
) {
4752 if (pos
>= fmt
.length
) assert(0, "out of format specifiers for arguments");
4753 assert(fmt
[pos
] == '%');
4755 if (pos
>= fmt
.length
) assert(0, "out of format specifiers for arguments");
4756 alias at
= XUQQ
!att
;
4757 bool doQuote
= false;
4758 switch (fmt
[pos
++]) {
4760 static if (is(at
== char)) {
4761 puts
!true(args
[argnum
]);
4762 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
4763 import std
.conv
: to
;
4764 puts
!true(to
!string(args
[argnum
]));
4765 } else static if (is(at
== bool)) {
4766 puts
!false(args
[argnum
] ?
"true" : "false");
4767 } else static if (is(at
== enum)) {
4768 bool dumpNum
= true;
4769 foreach (string mname
; __traits(allMembers
, at
)) {
4770 if (args
[argnum
] == __traits(getMember
, at
, mname
)) {
4777 if (dumpNum
) putint
!long(cast(long)args
[argnum
]);
4778 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
4779 putfloat(args
[argnum
]);
4780 } else static if (is(at
: const(char)[])) {
4781 puts
!true(args
[argnum
]);
4782 } else static if (is(at
: T
*, T
)) {
4783 assert(0, "can't put pointers");
4785 import std
.conv
: to
;
4786 puts
!true(to
!string(args
[argnum
]));
4792 if (inQ
) goto case 'q';
4793 static if (is(at
== char)) {
4795 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
4796 import std
.conv
: to
;
4797 puts(to
!string(args
[argnum
]));
4798 } else static if (is(at
== bool)) {
4799 puts(args
[argnum
] ?
"true" : "false");
4800 } else static if (is(at
== enum)) {
4801 bool dumpNum
= true;
4802 foreach (string mname
; __traits(allMembers
, at
)) {
4803 if (args
[argnum
] == __traits(getMember
, at
, mname
)) {
4810 if (dumpNum
) putint
!long(cast(long)args
[argnum
]);
4811 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
4812 putfloat(args
[argnum
]);
4813 } else static if (is(at
: const(char)[])) {
4815 } else static if (is(at
: T
*, T
)) {
4816 assert(0, "can't put pointers");
4818 import std
.conv
: to
;
4819 puts(to
!string(args
[argnum
]));
4823 assert(0, "invalid format specifier");
4827 while (pos
< fmt
.length
) {
4829 if (pos
>= fmt
.length
) break;
4830 assert(0, "out of args for format specifier");
4833 //conwriteln(concmdbuf[0..concmdbufpos]);
4834 if (cmddg
!is null) {
4835 if (stpos
!= usize
.max
) {
4836 cmddg(concmdbuf
[stpos
..concmdbufpos
]);
4844 /// get console variable value; doesn't do complex conversions! (thread-safe)
4845 public T
convar(T
) (ConString s
) {
4847 scope(exit
) consoleUnlock();
4848 return conGetVar
!T(s
);
4851 /// set console variable value; doesn't do complex conversions! (thread-safe)
4852 /// WARNING! this is instant action, execution queue and r/o (and other) flags are ignored!
4853 public void convar(T
) (ConString s
, T val
) {
4855 scope(exit
) consoleUnlock();
4856 conSetVar
!T(s
, val
);
4860 // ////////////////////////////////////////////////////////////////////////// //
4861 __gshared
char[] concmdbuf
;
4862 __gshared
uint concmdbufpos
;
4863 shared static this () { concmdbuf
.unsafeArraySetLength(65536); }
4866 private void unsafeArraySetLength(T
) (ref T
[] arr
, int newlen
) /*nothrow*/ {
4867 if (newlen
< 0 || newlen
>= int.max
/2) assert(0, "invalid number of elements in array");
4868 if (arr
.length
> newlen
) {
4869 arr
.length
= newlen
;
4870 arr
.assumeSafeAppend
;
4871 } else if (arr
.length
< newlen
) {
4872 auto optr
= arr
.ptr
;
4873 arr
.length
= newlen
;
4874 if (arr
.ptr
!is optr
) {
4875 import core
.memory
: GC
;
4877 if (optr
is GC
.addrOf(optr
)) GC
.setAttr(optr
, GC
.BlkAttr
.NO_INTERIOR
);
4883 void concmdPrepend (ConString s
) {
4884 if (s
.length
== 0) return; // nothing to do
4885 if (s
.length
>= int.max
/4) assert(0, "console command too long"); //FIXME
4886 uint reslen
= cast(uint)s
.length
+1;
4887 uint newlen
= concmdbufpos
+reslen
;
4888 if (newlen
<= concmdbufpos || newlen
>= int.max
/2-1024) assert(0, "console command too long"); //FIXME
4889 if (newlen
> concmdbuf
.length
) concmdbuf
.unsafeArraySetLength(newlen
+512);
4891 if (concmdbufpos
> 0) {
4892 import core
.stdc
.string
: memmove
;
4893 memmove(concmdbuf
.ptr
+reslen
, concmdbuf
.ptr
, concmdbufpos
);
4895 // put new test and '\n'
4896 concmdbuf
.ptr
[0..s
.length
] = s
[];
4897 concmdbuf
.ptr
[s
.length
] = '\n';
4898 concmdbufpos
+= reslen
;
4902 void concmdEnsureNewCommand () {
4903 if (concmdbufpos
> 0 && concmdbuf
[concmdbufpos
-1] != '\n') {
4904 if (concmdbuf
.length
-concmdbufpos
< 1) concmdbuf
.unsafeArraySetLength(cast(int)concmdbuf
.length
+512);
4906 concmdbuf
.ptr
[concmdbufpos
++] = '\n';
4910 package(iv
) void concmdAdd(bool ensureNewCommand
=true) (ConString s
) {
4912 if (concmdbuf
.length
-concmdbufpos
< s
.length
+1) {
4913 concmdbuf
.unsafeArraySetLength(cast(int)(concmdbuf
.length
+s
.length
-(concmdbuf
.length
-concmdbufpos
)+512));
4915 static if (ensureNewCommand
) {
4916 if (concmdbufpos
> 0 && concmdbuf
[concmdbufpos
-1] != '\n') concmdbuf
.ptr
[concmdbufpos
++] = '\n';
4918 concmdbuf
[concmdbufpos
..concmdbufpos
+s
.length
] = s
[];
4919 concmdbufpos
+= s
.length
;
4924 /** does console has anything in command queue? (not thread-safe)
4926 * note that it can be anything, including comment or some blank chars.
4929 * `true` is queue is empty.
4931 public bool conQueueEmpty () {
4932 return (concmdbufpos
== 0);
4936 /** execute commands added with `concmd()`. (thread-safe)
4938 * all commands added during execution of this function will be postponed for the next call.
4939 * call this function in your main loop to process all accumulated console commands.
4944 * maxlen = maximum queue size to process (0: don't process commands added while processing ;-)
4947 * "has more commands" flag (i.e. some new commands were added to queue)
4949 public bool conProcessQueue (uint maxlen
=0) {
4950 import core
.stdc
.stdlib
: realloc
;
4951 static char* tmpcmdbuf
= null;
4952 static uint tmpcmdbufsize
= 0;
4953 scope(exit
) { consoleLock(); clearPrependStr(); conHistShrinkBuf(); consoleUnlock(); } // do it here
4956 scope(exit
) consoleUnlock();
4957 if (concmdbufpos
== 0) return false;
4959 bool once
= (maxlen
== 0);
4960 if (once
) maxlen
= uint.max
;
4965 scope(exit
) consoleUnlock();
4966 auto ebuf
= concmdbufpos
;
4967 s
= concmdbuf
[0..ebuf
];
4968 version(iv_cmdcon_debug_wait
) conwriteln("** [", s
, "]; ebuf=", ebuf
);
4969 if (tmpcmdbufsize
< s
.length
) {
4970 tmpcmdbuf
= cast(char*)realloc(tmpcmdbuf
, s
.length
+1);
4971 if (tmpcmdbuf
is null) {
4972 // here, we are dead and fucked (the exact order doesn't matter)
4973 import core
.stdc
.stdlib
: abort
;
4974 import core
.stdc
.stdio
: fprintf
, stderr
;
4975 import core
.memory
: GC
;
4977 GC
.disable(); // yeah
4978 thread_suspendAll(); // stop right here, you criminal scum!
4979 fprintf(stderr
, "\n=== FATAL: OUT OF MEMORY IN COMMAND CONSOLE!\n");
4980 abort(); // die, you bitch!
4982 tmpcmdbufsize
= cast(uint)s
.length
+1;
4984 import core
.stdc
.string
: memcpy
;
4985 if (s
.length
) { memcpy(tmpcmdbuf
, s
.ptr
, s
.length
); s
= tmpcmdbuf
[0..s
.length
]; }
4987 if (maxlen
<= ebuf
) maxlen
= 0; else maxlen
-= ebuf
;
4990 if (s
.length
== 0) break;
4994 auto cmd
= conGetCommandStr(s
);
4995 if (cmd
is null) break;
4996 if (cmd
.length
> int.max
/8) { conwriteln("command too long"); continue; }
4998 version(iv_cmdcon_debug_wait
) conwriteln("** <", cmd
, ">");
4999 if (conExecute(cmd
)) {
5000 // "wait" pseudocommand hit; prepend what is left
5001 version(iv_cmdcon_debug_wait
) conwriteln(" :: rest=<", s
, ">");
5003 scope(exit
) consoleUnlock();
5004 if (ccWaitPrependStrLen
) { concmdPrepend(prependStr
); clearPrependStr(); }
5005 if (s
.length
) { setPrependStr(s
); concmdPrepend(prependStr
); clearPrependStr(); }
5009 version(iv_cmdcon_debug_wait
) conwriteln(" ++++++++++");
5010 } catch (Exception e
) {
5011 conwriteln("***ERROR: ", e
.msg
);
5014 if (once || maxlen
== 0) break;
5018 scope(exit
) consoleUnlock();
5019 if (ccWaitPrependStrLen
) { concmdPrepend(prependStr
); clearPrependStr(); }
5020 return (concmdbufpos
== 0);
5025 // ////////////////////////////////////////////////////////////////////////// //
5026 /** process command-line arguments, put 'em to console queue, remove from array (thread-safe).
5028 * just call this function from `main (string[] args)`, with `args`.
5030 * console args looks like:
5031 * +cmd arg arg +cmd arg arg
5034 * immediate = immediately call `conProcessQueue()` with some big limit
5035 * args = those arg vector passed to `main()`
5038 * `true` is any command was added to queue.
5040 public bool conProcessArgs(bool immediate
=false) (ref string
[] args
) {
5042 //scope(exit) consoleUnlock();
5044 bool ensureCmd
= true;
5045 auto ocbpos
= concmdbufpos
;
5047 void puts (const(char)[] s
...) {
5050 concmdEnsureNewCommand();
5053 concmdAdd
!false(" "); // argument delimiter
5055 // check if we need to quote arg
5056 bool doQuote
= false;
5057 foreach (char ch
; s
) if (ch
<= ' ' || ch
== 127 || ch
== '"' || ch
== '#') { doQuote
= true; break; }
5059 concmdAdd
!false("\"");
5060 foreach (immutable idx
, char ch
; s
) {
5061 if (ch
< ' ' || ch
== 127 || ch
== '"' || ch
== '\\') {
5062 import core
.stdc
.stdio
: snprintf
;
5064 auto len
= snprintf(buf
.ptr
, buf
.length
, "\\x%02x", cast(uint)ch
);
5065 if (len
<= 0) assert(0, "concmd: ooooops!");
5066 concmdAdd
!false(buf
[0..len
]);
5068 concmdAdd
!false(s
[idx
..idx
+1]);
5071 concmdAdd
!false("\"");
5078 static if (immediate
) bool res
= false;
5081 while (idx
< args
.length
) {
5082 string a
= args
[idx
++];
5083 if (a
.length
== 0) continue;
5084 if (a
== "--") break; // no more
5085 if (a
== "++" || a
== "+--") {
5086 // normal parsing, remove this
5088 foreach (immutable c
; idx
+1..args
.length
) args
[c
-1] = args
[c
];
5093 scope(exit
) ensureCmd
= true;
5096 while (idx
< args
.length
) {
5099 if (a
[0] == '+') break;
5104 foreach (immutable c
; idx
..args
.length
) args
[xidx
+c
-idx
] = args
[c
];
5105 args
.length
-= idx
-xidx
;
5107 // execute it immediately if we're asked to do so
5108 static if (immediate
) {
5109 if (concmdbufpos
> ocbpos
) {
5111 conProcessQueue(256*1024);
5112 ocbpos
= concmdbufpos
;
5118 debug(concmd_procargs
) {
5119 import core
.stdc
.stdio
: snprintf
;
5120 import core
.sys
.posix
.unistd
: STDERR_FILENO
, write
;
5121 if (concmdbufpos
> ocbpos
) {
5122 write(STDERR_FILENO
, "===\n".ptr
, 4);
5123 write(STDERR_FILENO
, concmdbuf
.ptr
+ocbpos
, concmdbufpos
-ocbpos
);
5124 write(STDERR_FILENO
, "\n".ptr
, 1);
5126 foreach (immutable aidx
, string a
; args
) {
5128 auto len
= snprintf(buf
.ptr
, buf
.length
, "%u: ", cast(uint)aidx
);
5129 write(STDERR_FILENO
, buf
.ptr
, len
);
5130 write(STDERR_FILENO
, a
.ptr
, a
.length
);
5131 write(STDERR_FILENO
, "\n".ptr
, 1);
5135 static if (immediate
) return res
; else return (concmdbufpos
> ocbpos
);
5139 // ////////////////////////////////////////////////////////////////////////// //
5141 static if (__traits(compiles
, (){import iv
.vfs
;}())) {
5142 enum CmdConHasVFS
= true;
5145 enum CmdConHasVFS
= false;
5146 import std
.stdio
: File
;
5148 private int xindexof (const(char)[] s
, const(char)[] pat
, int stpos
=0) nothrow @trusted @nogc {
5149 if (pat
.length
== 0 || pat
.length
> s
.length
) return -1;
5150 if (stpos
< 0) stpos
= 0;
5151 if (s
.length
> int.max
/4) s
= s
[0..int.max
/4];
5152 while (stpos
< s
.length
) {
5153 if (s
.length
-stpos
< pat
.length
) break;
5154 if (s
.ptr
[stpos
] == pat
.ptr
[0]) {
5155 if (s
.ptr
[stpos
..stpos
+pat
.length
] == pat
[]) return stpos
;
5162 shared static this () {
5163 conRegFunc
!((ConString fname
, bool silent
=false) {
5165 static if (CmdConHasVFS
) {
5166 auto fl
= VFile(fname
);
5168 auto fl
= File(fname
.idup
);
5171 if (sz
> 1024*1024*64) throw new Exception("script file too big");
5173 enum AbortCmd
= "!!abort!!";
5174 auto s
= new char[](cast(uint)sz
);
5175 static if (CmdConHasVFS
) {
5179 while (tbuf
.length
) {
5180 auto rd
= fl
.rawRead(tbuf
);
5181 if (rd
.length
== 0) throw new Exception("read error");
5182 tbuf
= tbuf
[rd
.length
..$];
5185 if (s
.xindexof(AbortCmd
) >= 0) {
5186 auto apos
= s
.xindexof(AbortCmd
);
5188 if (s
.length
-apos
<= AbortCmd
.length || s
[apos
+AbortCmd
.length
] <= ' ') {
5190 // it should be the first command, not commented
5192 while (pos
> 0 && s
[pos
-1] != '\n') {
5193 if (s
[pos
-1] != ' ' && s
[pos
-1] != '\t') { good
= false; break; }
5196 if (good
) { s
= s
[0..apos
]; break; }
5199 apos
= s
.xindexof(AbortCmd
, apos
+1);
5204 } catch (Exception e
) {
5205 if (!silent
) conwriteln("ERROR loading script \"", fname
, "\"");
5207 })("exec", "execute console script (name [silent_failure_flag])");