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*/;
37 import iv
.dynstring
; // to properly print them
39 version = iv_cmdcon_use_spinlock
;
41 //version = iv_cmdcon_debug_wait;
42 //version = iv_cmdcon_honest_ctfe_writer;
45 // ////////////////////////////////////////////////////////////////////////// //
46 /// use this in conGetVar, for example, to avoid allocations
47 public alias ConString
= const(char)[];
50 // ////////////////////////////////////////////////////////////////////////// //
51 public enum ConDump
: int { none
, stdout
, stderr
}
52 shared ConDump conStdoutFlag
= ConDump
.stdout
;
53 public @property ConDump
conDump () nothrow @trusted @nogc { pragma(inline
, true); import core
.atomic
: atomicLoad
; return atomicLoad(conStdoutFlag
); } /// write console output to ...
54 public @property void conDump (ConDump v
) nothrow @trusted @nogc { pragma(inline
, true); import core
.atomic
: atomicStore
; atomicStore(conStdoutFlag
, v
); } /// ditto
56 shared static this () {
57 conRegVar
!conStdoutFlag("console_dump", "dump console output (none, stdout, stderr)");
61 // ////////////////////////////////////////////////////////////////////////// //
62 version(iv_cmdcon_use_spinlock
) {
64 private __gshared AtomicSpinLockRecursive consoleSpinLock
;
66 import core
.sync
.mutex
: Mutex
;
67 __gshared Mutex consoleLocker
;
68 __gshared Mutex consoleWriteLocker
;
69 shared static this () {
70 consoleLocker
= new Mutex();
71 consoleWriteLocker
= new Mutex();
76 // ////////////////////////////////////////////////////////////////////////// //
77 enum isGShared(alias v
) = !__traits(compiles
, ()@safe{auto _
=&v
;}());
78 enum isShared(alias v
) = is(typeof(v
) == shared);
79 enum isGoodVar(alias v
) = isGShared
!v || isShared
!v
;
80 //alias isGoodVar = isGShared;
83 // ////////////////////////////////////////////////////////////////////////// //
85 private bool strEquCI (const(char)[] s0
, const(char)[] s1
) pure nothrow @trusted @nogc {
86 if (s0
.length
!= s1
.length
) return false;
87 foreach (immutable idx
, char c0
; s0
) {
88 // try the easiest case first
90 if (c0
== s1
[idx
]) continue;
92 if (c0
== s1
.ptr
[idx
]) continue;
94 c0 |
= 0x20; // convert to ascii lowercase
95 if (c0
< 'a' || c0
> 'z') return false; // it wasn't a letter, no need to check the second char
96 // c0 is guaranteed to be a lowercase ascii here
98 if (c0
!= (s1
[idx
]|
0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
100 if (c0
!= (s1
.ptr
[idx
]|
0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
107 // ////////////////////////////////////////////////////////////////////////// //
110 enum ConBufSize = 64;
112 enum ConBufSize = 256*1024;
115 // each line in buffer ends with '\n'; we don't keep offsets or lengthes, as
116 // it's fairly easy to search in buffer, and drawing console is not a common
117 // thing, so it doesn't have to be superfast.
118 __gshared
char* cbuf
= null;
119 __gshared
int cbufhead
, cbuftail
; // `cbuftail` points *at* last char
120 __gshared
bool cbufLastWasCR
= false;
121 __gshared
uint cbufcursize
= 0;
122 __gshared
uint cbufmaxsize
= 256*1024;
124 shared static this () {
125 import core
.stdc
.stdlib
: malloc
;
126 //cbuf.ptr[0] = '\n';
133 cbuf
= cast(char*)malloc(cbufcursize
);
134 if (cbuf
is null) assert(0, "cmdcon: out of memory!"); // unlikely case
135 cbuf
[0..cbufcursize
] = 0;
138 conRegVar("con_bufsize", "current size of console output buffer", (ConVarBase self
) => cbufcursize
, (ConVarBase self
, uint nv
) {}, ConVarAttr
.ReadOnly
);
139 conRegVar
!cbufmaxsize(8192, 512*1024*1024, "con_bufmaxsize", "maximum size of console output buffer");
142 private __gshared
uint changeCount
= 1;
143 public @property uint cbufLastChange () nothrow @trusted @nogc { pragma(inline
, true); import iv
.atomic
; return atomicLoad(changeCount
); } /// changed when something was written to console buffer
146 // ////////////////////////////////////////////////////////////////////////// //
147 version(aliced
) {} else {
148 private auto assumeNoThrowNoGC(T
) (scope T t
) /*if (isFunctionPointer!T || isDelegate!T)*/ {
150 enum attrs
= functionAttributes
!T|FunctionAttribute
.nogc|FunctionAttribute
.nothrow_
;
151 return cast(SetFunctionAttributes
!(T
, functionLinkage
!T
, attrs
)) t
;
157 public void consoleLock() () nothrow @trusted @nogc {
158 /*version(aliced)*/ pragma(inline
, true);
159 version(iv_cmdcon_use_spinlock
) {
160 consoleSpinLock
.lock();
163 consoleLocker
.lock();
165 //try { consoleLocker.lock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
166 assumeNoThrowNoGC(() { consoleLocker
.lock(); })();
171 /// multithread unlock
172 public void consoleUnlock() () nothrow @trusted @nogc {
173 /*version(aliced)*/ pragma(inline
, true);
174 version(iv_cmdcon_use_spinlock
) {
175 consoleSpinLock
.unlock();
178 consoleLocker
.unlock();
180 //try { consoleLocker.unlock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
181 assumeNoThrowNoGC(() { consoleLocker
.unlock(); })();
186 version(iv_cmdcon_use_spinlock
) {
187 public alias consoleWriteLock
= consoleLock
;
188 public alias consoleWriteUnlock
= consoleUnlock
;
191 public void consoleWriteLock() () nothrow @trusted @nogc {
192 version(aliced
) pragma(inline
, true);
194 consoleWriteLocker
.lock();
196 //try { consoleWriteLocker.lock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
197 assumeNoThrowNoGC(() { consoleWriteLocker
.lock(); })();
201 // multithread unlock
202 public void consoleWriteUnlock() () nothrow @trusted @nogc {
203 version(aliced
) pragma(inline
, true);
205 consoleWriteLocker
.unlock();
207 //try { consoleWriteLocker.unlock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
208 assumeNoThrowNoGC(() { consoleWriteLocker
.unlock(); })();
213 public __gshared
void delegate () nothrow @trusted @nogc conWasNewLineCB
; /// can be called in any thread; called if some '\n' was output with `conwrite*()`
216 // ////////////////////////////////////////////////////////////////////////// //
217 /// put characters to console buffer (and, possibly, STDOUT_FILENO). thread-safe.
218 public void cbufPut() (scope ConString chrs
...) nothrow @trusted @nogc { pragma(inline
, true); if (chrs
.length
) cbufPutIntr
!true(chrs
); }
220 public void cbufPutIntr(bool dolock
) (scope ConString chrs
...) nothrow @trusted @nogc {
221 if (!chrs
.length
) return;
222 import iv
.atomic
: atomicLoad
;
223 static if (dolock
) consoleWriteLock();
224 static if (dolock
) scope(exit
) consoleWriteUnlock();
225 immutable ConDump dtp
= cast(ConDump
)atomicLoad(*cast(int*)&conStdoutFlag
);
226 if (dtp
== ConDump
.stdout || dtp
== ConDump
.stderr
) {
229 import core
.sys
.windows
.winbase
;
230 import core
.sys
.windows
.windef
;
231 import core
.sys
.windows
.wincon
;
232 auto hh
= GetStdHandle(dtp
== ConDump
.stdout ? STD_OUTPUT_HANDLE
: STD_ERROR_HANDLE
);
233 if (hh
!= INVALID_HANDLE_VALUE
) {
236 while (xpos
< chrs
.length
) {
238 while (epos
< chrs
.length
&& chrs
.ptr
[epos
] != '\n') ++epos
;
239 if (epos
> xpos
) WriteConsoleA(hh
, chrs
.ptr
, cast(DWORD
)chrs
.length
, &ww
, null);
240 if (epos
< chrs
.length
) WriteConsoleA(hh
, "\r\n".ptr
, cast(DWORD
)2, &ww
, null);
245 import core
.sys
.posix
.unistd
: STDOUT_FILENO
, STDERR_FILENO
, write
;
246 immutable int fd
= (dtp
== ConDump
.stdout ? STDOUT_FILENO
: STDERR_FILENO
);
247 auto left
= chrs
.length
;
248 const(char)* p
= chrs
.ptr
;
250 uint wr
= (left
< 0x1fff_ffff ?
cast(uint)left
: 0x1fff_ffff
);
251 immutable res
= write(fd
, p
, wr
);
253 import core
.stdc
.errno
: errno
, EINTR
;
254 if (errno
== EINTR
) continue;
262 import iv
.atomic
: atomicFetchAdd
;
263 atomicFetchAdd(changeCount
, 1);
264 bool wasNewLine
= false;
265 foreach (char ch
; chrs
) {
266 if (cbufLastWasCR
&& ch
== '\x0a') { cbufLastWasCR
= false; continue; }
267 if ((cbufLastWasCR
= (ch
== '\x0d')) != false) ch
= '\x0a';
268 wasNewLine
= (wasNewLine ||
(ch
== '\x0a'));
269 int np
= (cbuftail
+1)%cbufcursize
;
270 if (np
== cbufhead
) {
271 // we have to make some room; delete top line for this
272 bool removelines
= true;
273 // first, try grow cosole buffer if we can
274 if (cbufcursize
< cbufmaxsize
) {
275 import core
.stdc
.stdlib
: realloc
;
277 auto newsz
= cbufcursize
+13;
279 auto newsz
= cbufcursize
*2;
281 if (newsz
> cbufmaxsize
) newsz
= cbufmaxsize
;
282 //{ import core.stdc.stdio : stderr, fprintf; stderr.fprintf("*** CON: %u -> %u; head=%u; tail=%u; np=%u\n", cbufcursize, newsz, cbufhead, cbuftail, np); }
283 auto nbuf
= cast(char*)realloc(cbuf
, newsz
);
285 // yay, we got some room; move text down, so there will be more room
286 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
); }
288 // yay, we can do some trickery here
289 np
= cbuftail
+1; // yep, that easy
291 import core
.stdc
.string
: memmove
;
292 auto tsz
= cbufcursize
-np
;
293 if (tsz
> 0) memmove(cbuf
+newsz
-tsz
, cbuf
+np
, tsz
);
294 cbufhead
+= newsz
-cbufcursize
;
299 version(test_cbuf
) { import core
.stdc
.stdio
: stderr
, fprintf
; stderr
.fprintf(" cbufhead=%u; cbuftail=%u\n", cbufhead
, cbuftail
); }
300 assert(np
< cbufcursize
);
305 char och
= cbuf
[cbufhead
];
306 cbufhead
= (cbufhead
+1)%cbufcursize
;
307 if (cbufhead
== np || och
== '\n') break;
314 if (wasNewLine
&& conWasNewLineCB
!is null) conWasNewLineCB();
318 // ////////////////////////////////////////////////////////////////////////// //
319 /// range of conbuffer lines, from last. not thread-safe.
320 /// warning! don't modify conbuf while the range is active!
321 public auto conbufLinesRev () nothrow @trusted @nogc {
323 nothrow @trusted @nogc:
325 int h
, t
; // head and tail, to check validity
329 @property auto front () const { pragma(inline
, true); return (sp
>= 0 ? cbuf
[sp
] : '\x00'); }
330 @property bool empty () const { pragma(inline
, true); return (sp
< 0 || h
!= cbufhead || t
!= cbuftail
); }
331 @property auto save () { pragma(inline
, true); return Line(h
, t
, sp
, ep
); }
332 void popFront () { pragma(inline
, true); if (sp
< 0 ||
(sp
= (sp
+1)%cbufcursize
) == ep
) sp
= -1; }
333 @property usize
opDollar () { pragma(inline
, true); return (sp
>= 0 ?
(sp
> ep ? ep
+cbufcursize
-sp
: ep
-sp
) : 0); }
334 alias length
= opDollar
;
335 char opIndex (usize pos
) { pragma(inline
, true); return (sp
>= 0 ? cbuf
[sp
+pos
] : '\x00'); }
338 static struct Range
{
339 nothrow @trusted @nogc:
341 int h
, t
; // head and tail, to check validity
342 int pos
; // position of prev line
345 void toLineStart () {
347 while (pos
!= cbufhead
) {
348 int p
= (pos
+cbufcursize
-1)%cbufcursize
;
349 if (cbuf
[p
] == '\n') break;
358 @property auto front () pure { pragma(inline
, true); return line
; }
359 @property bool empty () const { pragma(inline
, true); return (pos
< 0 || pos
== h || h
!= cbufhead || t
!= cbuftail
); }
360 @property auto save () { pragma(inline
, true); return Range(h
, t
, pos
, line
); }
362 if (pos
< 0 || pos
== h || h
!= cbufhead || t
!= cbuftail
) { line
= Line
.init
; h
= t
= pos
= -1; return; }
363 pos
= (pos
+cbufcursize
-1)%cbufcursize
;
370 res
.pos
= res
.t
= cbuftail
;
371 if (cbuf
[res
.pos
] != '\n') res
.pos
= (res
.pos
+1)%cbufcursize
;
373 //{ import std.stdio; writeln("pos=", res.pos, "; head=", res.h, "; tail=", res.t, "; llen=", res.line.length, "; [", res.line, "]"); }
378 // ////////////////////////////////////////////////////////////////////////// //
379 version(unittest) public void conbufDump () {
382 stdout
.writeln("==========================");
384 if (cbuf
[pp
] == '\n') stdout
.write('|');
385 stdout
.write(cbuf
[pp
]);
386 if (pp
== cbuftail
) {
387 if (cbuf
[pp
] != '\n') stdout
.write('\n');
390 pp
= (pp
+1)%cbufcursize
;
392 //foreach (/+auto+/ s; conbufLinesRev) stdout.writeln(s, "|");
396 // ////////////////////////////////////////////////////////////////////////// //
397 version(test_cbuf
) unittest {
399 cbufPut("boo\n"); conbufDump();
400 cbufPut("this is another line\n"); conbufDump();
401 cbufPut("one more line\n"); conbufDump();
402 cbufPut("foo\n"); conbufDump();
403 cbufPut("more lines!\n"); conbufDump();
404 cbufPut("and even more lines!\n"); conbufDump();
405 foreach (immutable idx
; 0..256) {
406 import std
.string
: format
;
407 cbufPut("line %s\n".format(idx
));
413 // ////////////////////////////////////////////////////////////////////////// //
415 private import std
.traits
/* : isBoolean, isIntegral, isPointer*/;
416 //private alias StripTypedef(T) = T;
418 void conwriter() (scope ConString
str) nothrow @trusted @nogc {
419 pragma(inline
, true);
420 if (str.length
> 0) cbufPut(str);
424 // ////////////////////////////////////////////////////////////////////////// //
425 // i am VERY sorry for this!
426 private template XUQQ(T
) {
427 static if (is(T
: shared TT
, TT
)) {
428 static if (is(TT
: const 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
: const TT
, TT
)) {
432 static if (is(TT
: shared TX
, TX
)) alias XUQQ
= TX
;
433 else static if (is(TT
: immutable TX
, TX
)) alias XUQQ
= TX
;
434 else alias XUQQ
= TT
;
435 } else static if (is(T
: immutable TT
, TT
)) {
436 static if (is(TT
: const TX
, TX
)) alias XUQQ
= TX
;
437 else static if (is(TT
: shared TX
, TX
)) alias XUQQ
= TX
;
438 else alias XUQQ
= TT
;
443 static assert(is(XUQQ
!string
== string
));
444 static assert(is(XUQQ
!(const(char)[]) == const(char)[]));
447 // ////////////////////////////////////////////////////////////////////////// //
448 private void cwrxputch (scope const(char)[] s
...) nothrow @trusted @nogc {
449 pragma(inline
, true);
450 if (s
.length
) cbufPutIntr
!false(s
); // no locking
454 private void cwrxputstr(bool cutsign
=false) (scope const(char)[] str, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
456 if (maxwdt
== maxwdt
.min
) ++maxwdt
; // alas
458 if (str.length
> maxwdt
) str = str[$-maxwdt
..$];
459 } else if (maxwdt
> 0) {
460 if (str.length
> maxwdt
) str = str[0..maxwdt
];
462 static if (cutsign
) {
463 if (signw
== ' ' && wdt
&& str.length
&& str.length
< wdt
) {
464 if (str.ptr
[0] == '-' ||
str.ptr
[0] == '+') {
465 cwrxputch(str.ptr
[0]);
471 if (str.length
< wdt
) {
472 // '+' means "center"
474 foreach (immutable _
; 0..(wdt
-str.length
)/2) cwrxputch(lchar
);
475 } else if (signw
!= '-') {
476 foreach (immutable _
; 0..wdt
-str.length
) cwrxputch(lchar
);
480 if (str.length
< wdt
) {
481 // '+' means "center"
483 foreach (immutable _
; ((wdt
-str.length
)/2)+str.length
..wdt
) cwrxputch(rchar
);
484 } else if (signw
== '-') {
485 foreach (immutable _
; 0..wdt
-str.length
) cwrxputch(rchar
);
491 private void cwrxputstrz(bool cutsign
=false) (scope const(char)* str, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
493 cwrxputstr
!cutsign("", signw
, lchar
, rchar
, wdt
, maxwdt
);
497 cwrxputstr
!cutsign(str[0..cast(usize
)(end
-str)], signw
, lchar
, rchar
, wdt
, maxwdt
);
502 private void cwrxputchar (char ch
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
503 cwrxputstr((&ch
)[0..1], signw
, lchar
, rchar
, wdt
, maxwdt
);
507 private void cwrxputint(TT
) (TT nn
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
510 static if (is(TT
== shared)) {
513 T n
= atomicLoad(nn
);
519 static if (is(T
== long)) {
520 if (n
== 0x8000_0000_0000_0000uL) { cwrxputstr
!true("-9223372036854775808", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
521 } else static if (is(T
== int)) {
522 if (n
== 0x8000_0000u) { cwrxputstr
!true("-2147483648", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
523 } else static if (is(T
== short)) {
524 if ((n
&0xffff) == 0x8000u
) { cwrxputstr
!true("-32768", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
525 } else static if (is(T
== byte)) {
526 if ((n
&0xff) == 0x80u
) { cwrxputstr
!true("-128", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
529 static if (__traits(isUnsigned
, T
)) {
536 int bpos
= buf
.length
;
538 //if (bpos == 0) assert(0, "internal printer error");
539 buf
.ptr
[--bpos
] = cast(char)('0'+n
%10);
543 //if (bpos == 0) assert(0, "internal printer error");
544 buf
.ptr
[--bpos
] = '-';
546 cwrxputstr
!true(buf
[bpos
..$], signw
, lchar
, rchar
, wdt
, maxwdt
);
550 private void cwrxputhex(TT
) (TT nn
, bool upcase
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
553 static if (is(TT
== shared)) {
556 T n
= atomicLoad(nn
);
562 static if (is(T
== long)) {
563 if (n
== 0x8000_0000_0000_0000uL) { cwrxputstr
!true("-8000000000000000", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
564 } else static if (is(T
== int)) {
565 if (n
== 0x8000_0000u) { cwrxputstr
!true("-80000000", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
566 } else static if (is(T
== short)) {
567 if (n
== 0x8000u
) { cwrxputstr
!true("-8000", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
568 } else static if (is(T
== byte)) {
569 if (n
== 0x80u
) { cwrxputstr
!true("-80", signw
, lchar
, rchar
, wdt
, maxwdt
); return; }
572 static if (__traits(isUnsigned
, T
)) {
579 int bpos
= buf
.length
;
581 //if (bpos == 0) assert(0, "internal printer error");
582 immutable ubyte b
= n
&0x0f;
584 if (b
< 10) buf
.ptr
[--bpos
] = cast(char)('0'+b
); else buf
.ptr
[--bpos
] = cast(char)((upcase ?
'A' : 'a')+(b
-10));
587 //if (bpos == 0) assert(0, "internal printer error");
588 buf
.ptr
[--bpos
] = '-';
590 cwrxputstr
!true(buf
[bpos
..$], signw
, lchar
, rchar
, wdt
, maxwdt
);
594 private void cwrxputfloat(TT
) (TT nn
, bool simple
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
595 import core
.stdc
.stdlib
: malloc
, realloc
;
597 static if (is(TT
== shared)) {
600 T n
= atomicLoad(nn
);
607 static usize buflen
= 0;
613 buf
= cast(char*)malloc(buflen
);
614 if (buf
is null) assert(0, "out of memory");
617 void putNum (int n
) {
618 if (n
== n
.min
) assert(0, "oops");
619 if (n
< 0) { fmtstr
.ptr
[fspos
++] = '-'; n
= -(n
); }
621 int bpos
= buf
.length
;
623 buf
.ptr
[--bpos
] = cast(char)('0'+n
%10);
626 if (fmtstr
.length
-fspos
< buf
.length
-bpos
) assert(0, "internal printer error");
627 fmtstr
.ptr
[fspos
..fspos
+(buf
.length
-bpos
)] = buf
[bpos
..$];
628 fspos
+= buf
.length
-bpos
;
631 fmtstr
.ptr
[fspos
++] = '%';
634 if (signw
== '-') fmtstr
.ptr
[fspos
++] = '-';
638 fmtstr
.ptr
[fspos
++] = '.';
641 fmtstr
.ptr
[fspos
++] = 'g';
644 fmtstr
.ptr
[fspos
++] = 'g';
646 fmtstr
.ptr
[fspos
++] = '\x00';
647 //{ import core.stdc.stdio; printf("<%s>", fmtstr.ptr); }
650 import core
.stdc
.stdio
: snprintf
;
651 auto plen
= snprintf(buf
, buflen
, fmtstr
.ptr
, cast(double)n
);
652 if (plen
>= buflen
) {
654 buf
= cast(char*)realloc(buf
, buflen
);
655 if (buf
is null) assert(0, "out of memory");
658 foreach (ref ch
; buf
[0..plen
]) {
659 if (ch
!= ' ') break;
664 foreach_reverse (ref ch
; buf
[0..plen
]) {
665 if (ch
!= ' ') break;
669 //{ import core.stdc.stdio; printf("<{%s}>", buf); }
670 cwrxputstr
!true(buf
[0..plen
], signw
, lchar
, rchar
, wdt
, maxwdt
);
677 private void cwrxputenum(TT
) (TT nn
, char signw
, char lchar
, char rchar
, int wdt
, int maxwdt
) nothrow @trusted @nogc {
678 static if (is(TT
== shared)) {
681 T n
= atomicLoad(nn
);
687 foreach (string mname
; __traits(allMembers
, T
)) {
688 if (n
== __traits(getMember
, T
, mname
)) {
689 cwrxputstr
!false(mname
, signw
, lchar
, rchar
, wdt
, maxwdt
);
693 static if (__traits(isUnsigned
, T
)) {
694 cwrxputint
!long(cast(long)n
, signw
, lchar
, rchar
, wdt
, maxwdt
);
696 cwrxputint
!ulong(cast(ulong)n
, signw
, lchar
, rchar
, wdt
, maxwdt
);
701 // ////////////////////////////////////////////////////////////////////////// //
702 /** write formatted string to console with runtime format string
703 * will try to not allocate if it is possible.
705 * understands [+|-]width[.maxlen]
706 * negative width: add spaces to right
707 * + signed width: center
708 * negative maxlen: get right part
710 * 's': use to!string to write argument
711 * note that writer can print strings, bools, integrals and floats without allocation
712 * 'x': write integer as hex
713 * 'X': write integer as HEX
714 * '!': skip all arguments that's left, no width allowed
715 * '%': just a percent sign, no width allowed
716 * '|': print all arguments that's left with simple "%s", no width allowed
717 * '<...>': print all arguments that's left with simple "%s", delimited with "...", no width allowed
718 * options (must immediately follow '%'): NOT YET
719 * '~': fill with the following char instead of space
720 * second '~': right filling char for 'center'
722 public void conprintfX(bool donl
, A
...) (ConString fmt
, in auto ref A args
) {
723 import core
.stdc
.stdio
: snprintf
;
724 char[128] tmp
= void;
728 void put (const(char)[] s
...) nothrow @trusted @nogc {
729 foreach (char ch
; s
) {
730 if (tlen
>= tmp
.length
) { tlen
= tmp
.length
+42; break; }
731 tmp
.ptr
[tlen
++] = ch
;
736 static struct IntNum
{ char sign
; int aval
; bool leadingZero
; /*|value|*/ }
738 static char[] utf8Encode (char[] s
, dchar c
) pure nothrow @trusted @nogc {
739 assert(s
.length
>= 4);
740 if (c
> 0x10FFFF) c
= '\uFFFD';
742 s
.ptr
[0] = cast(char)c
;
746 s
.ptr
[0] = cast(char)(0xC0|
(c
>>6));
747 s
.ptr
[1] = cast(char)(0x80|
(c
&0x3F));
749 } else if (c
<= 0xFFFF) {
750 s
.ptr
[0] = cast(char)(0xE0|
(c
>>12));
751 s
.ptr
[1] = cast(char)(0x80|
((c
>>6)&0x3F));
752 s
.ptr
[2] = cast(char)(0x80|
(c
&0x3F));
754 } else if (c
<= 0x10FFFF) {
755 s
.ptr
[0] = cast(char)(0xF0|
(c
>>18));
756 s
.ptr
[1] = cast(char)(0x80|
((c
>>12)&0x3F));
757 s
.ptr
[2] = cast(char)(0x80|
((c
>>6)&0x3F));
758 s
.ptr
[3] = cast(char)(0x80|
(c
&0x3F));
767 void parseInt(bool allowsign
) (ref IntNum n
) nothrow @trusted @nogc {
770 n
.leadingZero
= false;
771 if (fmt
.length
== 0) return;
772 static if (allowsign
) {
773 if (fmt
.ptr
[0] == '-' || fmt
.ptr
[0] == '+') {
776 if (fmt
.length
== 0) return;
779 if (fmt
.ptr
[0] < '0' || fmt
.ptr
[0] > '9') return;
780 n
.leadingZero
= (fmt
.ptr
[0] == '0');
781 while (fmt
.length
&& fmt
.ptr
[0] >= '0' && fmt
.ptr
[0] <= '9') {
782 n
.aval
= n
.aval
*10+(fmt
.ptr
[0]-'0'); // ignore overflow here
787 void skipLitPart () nothrow @trusted @nogc {
788 while (fmt
.length
> 0) {
789 import core
.stdc
.string
: memchr
;
790 auto prcptr
= cast(const(char)*)memchr(fmt
.ptr
, '%', fmt
.length
);
791 if (prcptr
is null) prcptr
= fmt
.ptr
+fmt
.length
;
792 // print (and remove) literal part
793 if (prcptr
!is fmt
.ptr
) {
794 auto lplen
= cast(usize
)(prcptr
-fmt
.ptr
);
795 cwrxputch(fmt
[0..lplen
]);
798 if (fmt
.length
>= 2 && fmt
.ptr
[0] == '%' && fmt
.ptr
[1] == '%') {
807 if (fmt
.length
== 0) return;
809 scope(exit
) consoleWriteUnlock();
810 auto fmtanchor
= fmt
;
814 ConString smpDelim
= null;
815 ConString fmtleft
= null;
817 argloop
: foreach (immutable argnum
, /*auto*/ att
; A
) {
819 // skip literal part of the format string
821 // something's left in the format string?
822 if (fmt
.length
> 0) {
823 // here we should have at least two chars
824 assert(fmt
[0] == '%');
825 if (fmt
.length
== 1) { cwrxputch("<stray-percent-in-conprintf>"); break argloop
; }
828 if (fmt
.length
&& fmt
[0] == '.') {
830 parseInt
!false(maxlen
);
834 maxlen
.leadingZero
= false;
836 if (fmt
.length
== 0) { cwrxputch("<stray-percent-in-conprintf>"); break argloop
; }
840 case '!': break argloop
; // no more
841 case '|': // rock 'till the end
842 wdt
.sign
= maxlen
.sign
= ' ';
843 wdt
.aval
= maxlen
.aval
= 0;
844 wdt
.leadingZero
= maxlen
.leadingZero
= false;
845 if (fmt
!is null) fmtleft
= fmt
;
848 case '<': // <...>: print all arguments that's left with simple "%s", delimited with "...", no width allowed
849 wdt
.sign
= maxlen
.sign
= ' ';
850 wdt
.aval
= maxlen
.aval
= 0;
851 wdt
.leadingZero
= maxlen
.leadingZero
= false;
853 while (epos
< fmt
.length
&& fmt
[epos
] != '>') ++epos
;
854 smpDelim
= fmt
[0..epos
];
855 if (epos
< fmt
.length
) ++epos
;
856 fmtleft
= fmt
[epos
..$];
859 case 's': // process as simple string
861 case 'd': case 'D': // decimal, process as simple string
862 case 'u': case 'U': // unsigned decimal, process as simple string
863 static if (is(at
== bool)) {
864 cwrxputint((args
[argnum
] ?
1 : 0), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
866 } else static if (is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar)) {
867 cwrxputint(cast(uint)(args
[argnum
] ?
1 : 0), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
869 } else static if (is(at
== enum)) {
870 static if (at
.min
>= int.min
&& at
.max
<= int.max
) {
871 cwrxputint(cast(int)args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
872 } else static if (at
.min
>= 0 && at
.max
<= uint.max
) {
873 cwrxputint(cast(uint)args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
874 } else static if (at
.min
>= long.min
&& at
.max
<= long.max
) {
875 cwrxputint(cast(long)args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
877 cwrxputint(cast(ulong)args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
883 case 'c': case 'C': // char
884 static if (is(at
== bool)) {
885 cwrxputstr
!false((args
[argnum
] ?
"t" : "f"), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
886 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
887 cwrxputstr
!false(utf8Encode(tmp
[], cast(dchar)args
[argnum
]), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
888 } else static if (is(at
== byte) ||
is(at
== short) ||
is(at
== int) ||
is(at
== long) ||
889 is(at
== ubyte) ||
is(at
== ushort) ||
is(at
== uint) ||
is(at
== ulong) ||
892 tmp
[0] = cast(char)args
[argnum
]; // nobody cares about unifuck
893 cwrxputstr
!false(tmp
[0..1], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
895 cwrxputch("<cannot-charprint-");
896 cwrxputch(at
.stringof
);
897 cwrxputch("in-conprintf>");
900 case 'x': case 'X': // hex
901 static if (is(at
== bool)) {
902 cwrxputint((args
[argnum
] ?
1 : 0), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
903 } else static if (is(at
== byte) ||
is(at
== short) ||
is(at
== int) ||
is(at
== long) ||
904 is(at
== ubyte) ||
is(at
== ushort) ||
is(at
== uint) ||
is(at
== ulong))
906 //cwrxputint(args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
907 cwrxputhex(args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
908 } else static if (is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar)) {
909 cwrxputhex(cast(uint)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
910 } else static if (is(at
== enum)) {
911 static if (at
.min
>= int.min
&& at
.max
<= int.max
) {
912 cwrxputhex(cast(int)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
913 } else static if (at
.min
>= 0 && at
.max
<= uint.max
) {
914 cwrxputhex(cast(uint)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
915 } else static if (at
.min
>= long.min
&& at
.max
<= long.max
) {
916 cwrxputhex(cast(long)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
918 cwrxputhex(cast(ulong)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
920 } else static if (is(at
: T
*, T
)) {
922 cwrxputhex(cast(usize
)args
[argnum
], (mode
== 'X'), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
924 cwrxputch("<cannot-hexprint-");
925 cwrxputch(at
.stringof
);
926 cwrxputch("in-conprintf>");
930 static if (is(at
== bool)) {
931 cwrxputfloat((args
[argnum
] ?
1.0f : 0.0f), false, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
932 } else static if (is(at
== byte) ||
is(at
== short) ||
is(at
== int) ||
is(at
== long) ||
933 is(at
== ubyte) ||
is(at
== ushort) ||
is(at
== uint) ||
is(at
== ulong) ||
934 is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar))
936 cwrxputfloat(cast(double)args
[argnum
], false, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
937 } else static if (is(at
== float) ||
is(at
== double)) {
938 cwrxputfloat(args
[argnum
], false, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
940 cwrxputch("<cannot-floatprint-");
941 cwrxputch(at
.stringof
);
942 cwrxputch("in-conprintf>");
946 cwrxputch("<invalid-format-in-conprintf(%");
947 if (mode
< 32 || mode
== 127) {
948 tlen
= snprintf(tmp
.ptr
, tmp
.length
, "\\x%02x", cast(uint)mode
);
949 cwrxputch(tmp
[0..tlen
]);
957 if (smpDelim
.length
) cwrxputch(smpDelim
);
958 wdt
.sign
= maxlen
.sign
= ' ';
959 wdt
.aval
= maxlen
.aval
= 0;
960 wdt
.leadingZero
= maxlen
.leadingZero
= false;
963 static if (is(at
== bool)) {
964 cwrxputstr
!false((args
[argnum
] ?
"true" : "false"), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
965 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
966 cwrxputstr
!false(utf8Encode(tmp
[], cast(dchar)args
[argnum
]), wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
967 } else static if (is(at
== byte) ||
is(at
== short) ||
is(at
== int) ||
is(at
== long) ||
968 is(at
== ubyte) ||
is(at
== ushort) ||
is(at
== uint) ||
is(at
== ulong))
970 cwrxputint(args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
971 } else static if (is(at
== char)) {
972 tmp
[0] = cast(char)args
[argnum
]; // nobody cares about unifuck
973 cwrxputstr
!false(tmp
[0..1], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
974 } else static if (is(at
== float) ||
is(at
== double)) {
975 cwrxputfloat(args
[argnum
], false, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
976 } else static if (is(at
== enum)) {
977 cwrxputenum(args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
978 } else static if (is(at
: T
*, T
)) {
980 static if (is(XUQQ
!T
== char)) {
981 // special case: `char*` will be printed as 0-terminated string
982 cwrxputstrz(args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
984 // other pointers will be printed as "0x..."
985 if (wdt
.aval
== 0) { wdt
.aval
= 8/*cast(int)usize.sizeof*2*/; wdt
.leadingZero
= true; }
986 cwrxputhex(cast(usize
)args
[argnum
], true, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
988 } else static if (is(at
== dynstring
)) {
990 cwrxputstr
!false(args
[argnum
].getData
, wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
991 } else static if (is(at
: const(char)[])) {
993 cwrxputstr
!false(args
[argnum
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
997 import std
.format
: formatValue
, singleSpec
;
1000 scope spec
= singleSpec("%s");
1001 formatValue(wrt
, args
[argnum
], spec
);
1002 if (tlen
> tmp
.length
) {
1003 cwrxputch("<error-formatting-in-conprintf>");
1005 cwrxputstr
!false(tmp
[0..tlen
], wdt
.sign
, (wdt
.leadingZero ?
'0' : ' '), (maxlen
.leadingZero ?
'0' : ' '), wdt
.aval
, maxlen
.aval
);
1007 } catch (Exception e
) {
1008 cwrxputch("<error-formatting-in-conprintf>");
1014 if (fmtleft
.length
) cwrxputch(fmtleft
);
1020 assert(fmt
[0] == '%');
1021 if (fmt
.length
== 1) {
1022 cwrxputch("<stray-percent-in-conprintf>");
1026 if (fmt
.length
&& fmt
[0] == '.') {
1028 parseInt
!false(maxlen
);
1030 if (fmt
.length
== 0) {
1031 cwrxputch("<stray-percent-in-conprintf>");
1035 if (mode
== '!' || mode
== '|') {
1036 if (fmt
.length
) cwrxputch(fmt
);
1037 } else if (mode
== '<') {
1038 while (fmt
.length
&& fmt
[0] != '>') fmt
= fmt
[1..$];
1039 if (fmt
.length
) fmt
= fmt
[1..$];
1040 if (fmt
.length
) cwrxputch(fmt
);
1042 cwrxputch("<orphan-format-in-conprintf(%");
1043 if (mode
< 32 || mode
== 127) {
1044 tlen
= snprintf(tmp
.ptr
, tmp
.length
, "\\x%02x", cast(uint)mode
);
1045 cwrxputch(tmp
[0..tlen
]);
1056 static if (donl
) cwrxputch("\n");
1059 public void conprintf(A
...) (ConString fmt
, in auto ref A args
) { conprintfX
!false(fmt
, args
); }
1060 public void conprintfln(A
...) (ConString fmt
, in auto ref A args
) { conprintfX
!true(fmt
, args
); }
1063 // ////////////////////////////////////////////////////////////////////////// //
1064 version(iv_cmdcon_honest_ctfe_writer
)
1065 /// write formatted string to console with compile-time format string
1066 public template conwritef(string fmt
, A
...) {
1067 private string
gen() () {
1071 //string simpledelim;
1073 uint simpledelimpos
;
1075 res
.length
= fmt
.length
+128;
1077 void putRes (const(char)[] s
...) {
1078 //if (respos+s.length > res.length) res.length = respos+s.length+256;
1079 foreach (char ch
; s
) {
1080 if (respos
>= res
.length
) res
.length
= res
.length
*2;
1085 void putSD (const(char)[] s
...) {
1086 if (simpledelim
.length
== 0) simpledelim
.length
= 128;
1087 foreach (char ch
; s
) {
1088 if (simpledelimpos
>= simpledelim
.length
) simpledelim
.length
= simpledelim
.length
*2;
1089 simpledelim
[simpledelimpos
++] = ch
;
1093 void putNum(bool hex
=false) (int n
, int minlen
=-1) {
1094 if (n
== n
.min
) assert(0, "oops");
1095 if (n
< 0) { putRes('-'); n
= -(n
); }
1097 int bpos
= buf
.length
;
1100 buf
[--bpos
] = "0123456789abcdef"[n
&0x0f];
1103 buf
[--bpos
] = cast(char)('0'+n
%10);
1107 } while (n
!= 0 || minlen
> 0);
1108 putRes(buf
[bpos
..$]);
1111 int parseNum (ref char sign
, ref bool leadzero
) {
1114 if (pos
>= fmt
.length
) return 0;
1115 if (fmt
[pos
] == '-' || fmt
[pos
] == '+') sign
= fmt
[pos
++];
1116 if (pos
>= fmt
.length
) return 0;
1118 if (pos
< fmt
.length
&& fmt
[pos
] == '0') leadzero
= true;
1119 while (pos
< fmt
.length
&& fmt
[pos
] >= '0' && fmt
[pos
] <= '9') res
= res
*10+fmt
[pos
++]-'0';
1123 void processUntilFSp () {
1124 while (pos
< fmt
.length
) {
1126 while (epos
< fmt
.length
&& fmt
[epos
] != '%') {
1127 if (fmt
[epos
] < ' ' && fmt
[epos
] != '\t' && fmt
[epos
] != '\n') break;
1128 if (fmt
[epos
] >= 127 || fmt
[epos
] == '`') break;
1132 putRes("cwrxputch(`");
1133 putRes(fmt
[pos
..epos
]);
1136 if (pos
>= fmt
.length
) break;
1138 if (fmt
[pos
] != '%') {
1139 putRes("cwrxputch('\\x");
1140 putNum
!true(cast(ubyte)fmt
[pos
], 2);
1145 if (fmt
.length
-pos
< 2 || fmt
[pos
+1] == '%') { putRes("cwrxputch('%');\n"); pos
+= 2; continue; }
1150 bool simples
= false;
1151 bool putsimpledelim
= false;
1152 char lchar
=' ', rchar
=' ';
1154 bool leadzerow
= false;
1158 argloop
: foreach (immutable argnum
, /*auto*/ att
; A
) {
1159 alias at
= XUQQ
!att
;
1162 if (pos
>= fmt
.length
) assert(0, "out of format specifiers for arguments");
1163 assert(fmt
[pos
] == '%');
1165 if (pos
< fmt
.length
&& fmt
[pos
] == '!') { ++pos
; break; } // skip rest
1166 if (pos
< fmt
.length
&& fmt
[pos
] == '|') {
1169 } else if (pos
< fmt
.length
&& fmt
[pos
] == '<') {
1173 while (ep
< fmt
.length
) {
1174 if (fmt
[ep
] == '>') break;
1175 if (fmt
[ep
] == '\\') ++ep
;
1178 if (ep
>= fmt
.length
) assert(0, "invalid format string");
1181 bool hasQuote
= false;
1182 foreach (char ch
; fmt
[pos
..ep
]) if (ch
== '\\' ||
(ch
< ' ' && ch
!= '\n' && ch
!= '\t') || ch
>= 127 || ch
== '`') { hasQuote
= true; break; }
1184 putSD("cwrxputch(`");
1185 putSD(fmt
[pos
..ep
]);
1188 //FIXME: get rid of char-by-char processing!
1189 putSD("cwrxputch(\"");
1191 char ch
= fmt
[pos
++];
1192 if (ch
== '\\') ch
= fmt
[pos
++];
1193 if (ch
== '\\' || ch
< ' ' || ch
>= 127 || ch
== '"') {
1195 putSD("0123456789abcdef"[ch
>>4]);
1196 putSD("0123456789abcdef"[ch
&0x0f]);
1206 lchar
= rchar
= ' ';
1208 if (pos
< fmt
.length
&& fmt
[pos
] == '~') {
1210 if (fmt
.length
-pos
< 2) assert(0, "invalid format string");
1213 if (pos
< fmt
.length
&& fmt
[pos
] == '~') {
1214 if (fmt
.length
-pos
< 2) assert(0, "invalid format string");
1219 wdt
= parseNum(signw
, leadzerow
);
1220 if (pos
>= fmt
.length
) assert(0, "invalid format string");
1221 if (!lrset
&& leadzerow
) lchar
= '0';
1222 if (fmt
[pos
] == '.') {
1226 maxwdt
= parseNum(mws
, lzw
);
1227 if (mws
== '-') maxwdt
= -maxwdt
;
1231 if (pos
>= fmt
.length
) assert(0, "invalid format string");
1236 lchar
= rchar
= signw
= ' ';
1240 if (putsimpledelim
&& simpledelimpos
> 0) {
1241 putRes(simpledelim
[0..simpledelimpos
]);
1243 putsimpledelim
= true;
1250 static if (is(at
== char)) {
1251 putRes("cwrxputchar(args[");
1254 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
1255 putRes("import std.conv : to; cwrxputstr(to!string(args[");
1258 } else static if (is(at
== bool)) {
1259 putRes("cwrxputstr((args[");
1261 putRes("] ? `true` : `false`)");
1262 } else static if (is(at
== enum)) {
1263 putRes("cwrxputenum(args[");
1266 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
1267 putRes("cwrxputfloat(args[");
1270 } else static if (is(at
: const(char)[])) {
1271 putRes("cwrxputstr(args[");
1274 } else static if (is(at
: T
*, T
)) {
1275 //putRes("cwrxputch(`0x`); ");
1276 static if (is(T
== char)) {
1277 putRes("cwrxputstrz(args[");
1281 if (wdt
< (void*).sizeof
*2) { lchar
= '0'; wdt
= cast(int)((void*).sizeof
)*2; signw
= ' '; }
1282 putRes("cwrxputhex(cast(usize)args[");
1286 } else static if (is(at
: long)) {
1287 putRes("cwrxputint(args[");
1291 putRes("import std.conv : to; cwrxputstr(to!string(args[");
1297 static if (is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar)) {
1298 putRes("cwrxputhex(cast(uint)args[");
1301 } else static if (is(at
== bool)) {
1302 putRes("cwrxputstr((args[");
1304 putRes("] ? `1` : `0`)");
1305 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
1306 assert(0, "can't hexprint floats yet");
1307 } else static if (is(at
: T
*, T
)) {
1308 if (wdt
< (void*).sizeof
*2) { lchar
= '0'; wdt
= cast(int)((void*).sizeof
)*2; signw
= ' '; }
1309 putRes("cwrxputhex(cast(usize)args[");
1312 } else static if (is(at
: long)) {
1313 putRes("cwrxputhex(args[");
1317 assert(0, "can't print '"~at
.stringof
~"' as hex");
1321 static if (is(at
== char) ||
is(at
== wchar) ||
is(at
== dchar)) {
1322 putRes("cwrxputhex(cast(uint)args[");
1325 } else static if (is(at
== bool)) {
1326 putRes("cwrxputstr((args[");
1328 putRes("] ? `1` : `0`)");
1329 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
1330 assert(0, "can't hexprint floats yet");
1331 } else static if (is(at
: T
*, T
)) {
1332 if (wdt
< (void*).sizeof
*2) { lchar
= '0'; wdt
= cast(int)((void*).sizeof
)*2; signw
= ' '; }
1333 putRes("cwrxputhex(cast(usize)args[");
1336 } else static if (is(at
: long)) {
1337 putRes("cwrxputhex(args[");
1341 assert(0, "can't print '"~at
.stringof
~"' as hex");
1345 static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
1346 putRes("cwrxputfloat(args[");
1350 assert(0, "can't print '"~at
.stringof
~"' as float");
1353 default: assert(0, "invalid format specifier: '"~fmtch
~"'");
1358 putNum
!true(cast(uint)lchar
, 2);
1360 putNum
!true(cast(uint)rchar
, 2);
1367 while (pos
< fmt
.length
) {
1369 if (pos
>= fmt
.length
) break;
1370 assert(fmt
[pos
] == '%');
1372 if (pos
< fmt
.length
&& (fmt
[pos
] == '!' || fmt
[pos
] == '|')) { ++pos
; continue; } // skip rest
1373 if (pos
< fmt
.length
&& fmt
[pos
] == '<') {
1375 while (pos
< fmt
.length
) {
1376 if (fmt
[pos
] == '>') break;
1377 if (fmt
[pos
] == '\\') ++pos
;
1383 assert(0, "too many format specifiers");
1385 return res
[0..respos
];
1387 void conwritef (A args
) {
1388 //enum code = gen();
1389 //pragma(msg, code);
1390 //pragma(msg, "===========");
1392 scope(exit
) consoleWriteUnlock();
1398 // ////////////////////////////////////////////////////////////////////////// //
1401 //void conwritef(string fmt, A...) (A args) { fdwritef!(fmt)(args); }
1402 version(iv_cmdcon_honest_ctfe_writer
) {
1403 void conwritefln(string fmt
, A
...) (A args
) { conwritef
!(fmt
)(args
); cwrxputch('\n'); } /// write formatted string to console with compile-time format string
1404 void conwrite(A
...) (A args
) { conwritef
!("%|")(args
); } /// write formatted string to console with compile-time format string
1405 void conwriteln(A
...) (A args
) { conwritef
!("%|\n")(args
); } /// write formatted string to console with compile-time format string
1407 public void conwritef(string fmt
, A
...) (in auto ref A args
) { conprintfX
!false(fmt
, args
); } /// unhonest CTFE writer
1408 void conwritefln(string fmt
, A
...) (in auto ref A args
) { conprintfX
!true(fmt
, args
); } /// write formatted string to console with compile-time format string
1409 void conwrite(A
...) (in auto ref A args
) { conprintfX
!false("%|", args
); } /// write formatted string to console with compile-time format string
1410 void conwriteln(A
...) (in auto ref A args
) { conprintfX
!false("%|\n", args
); } /// write formatted string to console with compile-time format string
1414 // ////////////////////////////////////////////////////////////////////////// //
1415 version(conwriter_test
)
1418 override string
toString () const { return "{A}"; }
1421 char[] n
= ['x', 'y', 'z'];
1422 char[3] t
= "def";//['d', 'e', 'f'];
1423 conwriter("========================\n");
1424 conwritef
!"`%%`\n"();
1425 conwritef
!"`%-3s`\n"(42);
1426 conwritef
!"<`%3s`%%{str=%s}%|>\n"(cast(int)42, "[a]", new A(), n
, t
[]);
1427 //conwritefln!"<`%3@%3s`>%!"(cast(int)42, "[a]", new A(), n, t);
1428 //errwriteln("stderr");
1429 conwritefln
!"`%-3s`"(42);
1430 //conwritefln!"`%!z%-2@%-3s`%!"(69, 42, 666);
1431 //conwritefln!"`%!%1@%-3s%!`"(69, 42, 666);
1432 //conwritefln!"`%!%-1@%+0@%-3s%!`"(69, 42, 666);
1433 conwritefln
!"`%3.5s`"("a");
1434 conwritefln
!"`%7.5s`"("abcdefgh");
1435 conwritef
!"%|\n"(42, 666);
1436 conwritefln
!"`%+10.5s`"("abcdefgh");
1437 conwritefln
!"`%+10.-5s`"("abcdefgh");
1438 conwritefln
!"`%~++10.-5s`"("abcdefgh");
1439 conwritefln
!"`%~+~:+10.-5s`"("abcdefgh");
1440 //conwritef!"%\0<>\0|\n"(42, 666, 999);
1441 //conwritef!"%\0\t\0|\n"(42, 666, 999);
1442 conwritefln
!"`%~*05s %~.5s`"(42, 666);
1443 conwritef
!"`%s`\n"(t
);
1444 conwritef
!"`%08s`\n"("alice");
1445 conwritefln
!"#%08x"(16396);
1446 conwritefln
!"#%08X"(-16396);
1447 conwritefln
!"#%02X"(-16385);
1448 conwritefln
!"[%06s]"(-666);
1449 conwritefln
!"[%06s]"(cast(long)0x8000_0000_0000_0000uL);
1450 conwritefln
!"[%06x]"(cast(long)0x8000_0000_0000_0000uL);
1452 void wrflt () nothrow @nogc {
1453 conwriteln(42.666f);
1454 conwriteln(cast(double)42.666);
1458 //immutable char *strz = "stringz\0s";
1459 //conwritefln!"[%S]"(strz);
1463 // ////////////////////////////////////////////////////////////////////////// //
1464 /// dump variables. use like this: `mixin condump!("x", "y")` ==> "x = 5, y = 3"
1465 mixin template condump (Names
...) {
1466 auto _xdump_tmp_
= {
1467 import iv
.cmdcon
: conwrite
;
1468 foreach (/*auto*/ i
, /*auto*/ name
; Names
) conwrite(name
, " = ", mixin(name
), (i
< Names
.length
-1 ?
", " : "\n"));
1474 version(conwriter_test_dump
)
1480 mixin condump
!("x", "y"); // x = 5, y = 3
1481 mixin condump
!("z"); // z = 15
1482 mixin condump
!("x+y"); // x+y = 8
1483 mixin condump
!("x+y < z"); // x+y < z = true
1489 // ////////////////////////////////////////////////////////////////////////// //
1492 /// base console command class
1493 public class ConCommand
{
1495 public import std
.conv
: ConvException
, ConvOverflowException
;
1497 // this is hack to avoid allocating error exceptions
1498 // don't do that at home!
1500 __gshared ConvException exBadNum
;
1501 __gshared ConvException exBadStr
;
1502 __gshared ConvException exBadBool
;
1503 __gshared ConvException exBadInt
;
1504 __gshared ConvException exBadEnum
;
1505 __gshared ConvOverflowException exIntOverflow
;
1506 __gshared ConvException exBadHexEsc
;
1507 __gshared ConvException exBadEscChar
;
1508 __gshared ConvException exNoArg
;
1509 __gshared ConvException exTooManyArgs
;
1510 __gshared ConvException exBadArgType
;
1512 shared static this () {
1513 exBadNum
= new ConvException("invalid number");
1514 exBadStr
= new ConvException("invalid string");
1515 exBadBool
= new ConvException("invalid boolean");
1516 exBadInt
= new ConvException("invalid integer number");
1517 exBadEnum
= new ConvException("invalid enumeration value");
1518 exIntOverflow
= new ConvOverflowException("overflow in integral conversion");
1519 exBadHexEsc
= new ConvException("invalid hex escape");
1520 exBadEscChar
= new ConvException("invalid escape char");
1521 exNoArg
= new ConvException("argument expected");
1522 exTooManyArgs
= new ConvException("too many arguments");
1523 exBadArgType
= new ConvException("can't parse given argument type (internal error)");
1527 alias ArgCompleteCB
= void delegate (ConCommand self
); /// prototype for argument completion callback
1530 __gshared usize wordBufMem
; // buffer for `getWord()`
1531 __gshared
uint wordBufUsed
, wordBufSize
;
1532 ArgCompleteCB argcomplete
; // this delegate will be called to do argument autocompletion
1534 static void wbReset () nothrow @trusted @nogc { pragma(inline
, true); wordBufUsed
= 0; }
1536 static void wbPut (const(char)[] s
...) nothrow @trusted @nogc {
1537 if (s
.length
== 0) return;
1538 if (s
.length
> int.max
/4) assert(0, "oops: console command too long");
1539 if (wordBufUsed
+cast(uint)s
.length
> int.max
/4) assert(0, "oops: console command too long");
1540 // grow wordbuf, if necessary
1541 if (wordBufUsed
+cast(uint)s
.length
> wordBufSize
) {
1542 import core
.stdc
.stdlib
: realloc
;
1543 wordBufSize
= ((wordBufUsed
+cast(uint)s
.length
)|
0x3fff)+1;
1544 auto newmem
= cast(usize
)realloc(cast(void*)wordBufMem
, wordBufSize
);
1545 if (newmem
== 0) assert(0, "cmdcon: out of memory");
1546 wordBufMem
= cast(usize
)newmem
;
1548 assert(wordBufUsed
+cast(uint)s
.length
<= wordBufSize
);
1549 (cast(char*)(wordBufMem
+wordBufUsed
))[0..s
.length
] = s
;
1550 wordBufUsed
+= cast(uint)s
.length
;
1553 static @property const(char)[] wordBuf () nothrow @trusted @nogc { pragma(inline
, true); return (cast(char*)wordBufMem
)[0..wordBufUsed
]; }
1559 this (string aname
, string ahelp
=null) { name
= aname
; help
= ahelp
; } ///
1561 void showHelp () { conwriteln(name
, " -- ", help
); } ///
1564 /// cmdline doesn't contain command name
1565 void exec (ConString cmdline
) {
1566 auto w
= getWord(cmdline
);
1567 if (w
== "?") showHelp
;
1571 final @property ConCommand
completer (ArgCompleteCB cb
) { version(aliced
) pragma(inline
, true); argcomplete
= cb
; return this; }
1572 final @property ArgCompleteCB
completer () pure { version(aliced
) pragma(inline
, true); return argcomplete
; }
1575 /// parse ch as digit in given base. return -1 if ch is not a valid digit.
1576 int digit(TC
) (TC ch
, uint base
) pure nothrow @safe @nogc if (isSomeChar
!TC
) {
1578 if (ch
>= '0' && ch
<= '9') res
= ch
-'0';
1579 else if (ch
>= 'A' && ch
<= 'Z') res
= ch
-'A'+10;
1580 else if (ch
>= 'a' && ch
<= 'z') res
= ch
-'a'+10;
1582 return (res
>= base ?
-1 : res
);
1585 /** get word from string.
1587 * it will correctly parse quoted strings.
1589 * note that next call to `getWord()` can destroy result.
1590 * returns `null` if there are no more words.
1591 * `*strtemp` will be `true` if temporary string storage was used.
1593 ConString
getWord (ref ConString s
, bool *strtemp
=null) {
1595 if (strtemp
!is null) *strtemp
= false;
1597 while (s
.length
> 0 && s
.ptr
[0] <= ' ') s
= s
[1..$];
1598 if (s
.length
== 0) return null;
1600 if (s
.ptr
[0] == '"' || s
.ptr
[0] == '\'') {
1601 char qch
= s
.ptr
[0];
1604 bool hasSpecial
= false;
1605 while (pos
< s
.length
&& s
.ptr
[pos
] != qch
) {
1606 if (s
.ptr
[pos
] == '\\') { hasSpecial
= true; break; }
1609 // simple quoted string?
1611 auto res
= s
[0..pos
];
1612 if (pos
< s
.length
) ++pos
; // skip closing quote
1616 if (strtemp
!is null) *strtemp
= true;
1617 //wordBuf.assumeSafeAppend.length = pos;
1618 //if (pos) wordBuf[0..pos] = s[0..pos];
1619 if (pos
) wbPut(s
[0..pos
]);
1620 // process special chars
1621 while (pos
< s
.length
&& s
.ptr
[pos
] != qch
) {
1622 if (s
.ptr
[pos
] == '\\' && s
.length
-pos
> 1) {
1624 switch (s
.ptr
[pos
++]) {
1625 case '"': case '\'': case '\\': wbPut(s
.ptr
[pos
-1]); break;
1626 case '0': wbPut('\x00'); break;
1627 case 'a': wbPut('\a'); break;
1628 case 'b': wbPut('\b'); break;
1629 case 'e': wbPut('\x1b'); break;
1630 case 'f': wbPut('\f'); break;
1631 case 'n': wbPut('\n'); break;
1632 case 'r': wbPut('\r'); break;
1633 case 't': wbPut('\t'); break;
1634 case 'v': wbPut('\v'); break;
1637 foreach (immutable _
; 0..2) {
1638 if (pos
>= s
.length
) throw exBadHexEsc
;
1639 char c2
= s
.ptr
[pos
++];
1640 if (digit(c2
, 16) < 0) throw exBadHexEsc
;
1641 n
= n
*16+digit(c2
, 16);
1645 default: throw exBadEscChar
;
1649 wbPut(s
.ptr
[pos
++]);
1651 if (pos
< s
.length
) ++pos
; // skip closing quote
1657 while (pos
< s
.length
&& s
.ptr
[pos
] > ' ') ++pos
;
1658 auto res
= s
[0..pos
];
1664 /// parse value of type T.
1665 T
parseType(T
) (ref ConString s
) {
1666 import std
.utf
: byCodeUnit
;
1668 static if (is(UT
== enum)) {
1670 auto w
= getWord(s
);
1672 foreach (string mname
; __traits(allMembers
, UT
)) {
1673 if (mname
== w
) return __traits(getMember
, UT
, mname
);
1676 foreach (string mname
; __traits(allMembers
, UT
)) {
1677 if (strEquCI(mname
, w
)) return __traits(getMember
, UT
, mname
);
1680 if (w
.length
< 1) throw exBadEnum
;
1684 long num
= parseInt
!long(ns
);
1685 if (ns
.length
> 0) throw exBadEnum
;
1686 foreach (string mname
; __traits(allMembers
, UT
)) {
1687 if (__traits(getMember
, UT
, mname
) == num
) return __traits(getMember
, UT
, mname
);
1690 ulong num
= parseInt
!ulong(ns
);
1691 if (ns
.length
> 0) throw exBadEnum
;
1692 foreach (string mname
; __traits(allMembers
, UT
)) {
1693 if (__traits(getMember
, UT
, mname
) == num
) return __traits(getMember
, UT
, mname
);
1696 } catch (Exception
) {}
1698 } else static if (is(UT
== bool)) {
1700 auto w
= getWord(s
);
1701 if (w
is null) throw exNoArg
;
1703 auto res
= parseBool(w
, true, &good
);
1704 if (!good
) throw exBadBool
;
1706 } else static if ((isIntegral
!UT || isFloatingPoint
!UT
) && !is(UT
== enum)) {
1708 auto w
= getWord(s
);
1709 if (w
is null) throw exNoArg
;
1710 bool goodbool
= false;
1711 auto bv
= parseBool(w
, false, &goodbool
); // no numbers
1713 return cast(UT
)(bv ?
1 : 0);
1715 auto ss
= w
.byCodeUnit
;
1716 auto res
= parseNum
!UT(ss
);
1717 if (!ss
.empty
) throw exBadNum
;
1720 } else static if (is(UT
: ConString
)) {
1723 auto w
= getWord(s
);
1724 if (w
is null) throw exNoArg
;
1725 if (s
.length
&& s
.ptr
[0] > 32) throw exBadStr
;
1727 // temp storage was used
1728 static if (is(UT
== string
)) return w
.idup
; else return w
.dup
;
1730 // no temp storage was used
1731 static if (is(UT
== ConString
)) return w
;
1732 else static if (is(UT
== string
)) return w
.idup
;
1735 } else static if (is(UT
: char)) {
1738 auto w
= getWord(s
);
1739 if (w
is null || w
.length
!= 1) throw exNoArg
;
1740 if (s
.length
&& s
.ptr
[0] > 32) throw exBadStr
;
1747 /// parse boolean value
1748 public static bool parseBool (ConString s
, bool allowNumbers
=true, bool* goodval
=null) nothrow @trusted @nogc {
1750 if (goodval
!is null) *goodval
= false;
1751 while (s
.length
> 0 && s
[0] <= ' ') s
= s
[1..$];
1752 while (s
.length
> 0 && s
[$-1] <= ' ') s
= s
[0..$-1];
1753 if (s
.length
> tbuf
.length
) return false;
1755 foreach (char ch
; s
) {
1756 if (ch
>= 'A' && ch
<= 'Z') ch
+= 32; // poor man's tolower
1757 tbuf
.ptr
[pos
++] = ch
;
1759 switch (tbuf
[0..pos
]) {
1761 case "yes": case "tan":
1762 case "true": case "on":
1763 if (goodval
!is null) *goodval
= true;
1765 case "1": case "-1": case "42":
1767 if (goodval
!is null) *goodval
= true;
1772 case "no": case "ona":
1773 case "false": case "off":
1774 if (goodval
!is null) *goodval
= true;
1778 if (goodval
!is null) *goodval
= true;
1787 /** parse integer number.
1789 * parser checks for overflows and understands different bases (0x, 0b, 0o, 0d).
1790 * parser skips leading spaces. stops on first non-numeric char.
1794 * s = input range; will be modified
1800 * ConvException or ConvOverflowException
1802 public static T
parseInt(T
, TS
) (ref TS s
) if (isSomeChar
!(ElementType
!TS
) && isIntegral
!T
&& !is(T
== enum)) {
1803 import std
.traits
: isSigned
;
1806 static if (isSigned
!T
) bool neg = false;
1809 if (s
.front
> 32) break;
1812 if (s
.empty
) throw exBadInt
;
1815 case '+': // it's ok
1819 static if (isSigned
!T
) {
1826 default: // do nothing
1828 if (s
.empty
) throw exBadInt
;
1829 // check for various bases
1830 if (s
.front
== '0') {
1832 if (s
.empty
) return cast(T
)0;
1834 switch (/*auto ch = s.front*/ch
) {
1835 case 'b': case 'B': base
= 2; goto gotbase
;
1836 case 'o': case 'O': base
= 8; goto gotbase
;
1837 case 'd': case 'D': base
= 10; goto gotbase
;
1838 case 'x': case 'X': base
= 16;
1841 goto checkfirstdigit
;
1843 if (ch
!= '_' && digit(ch
, base
) < 0) throw exBadInt
;
1847 // no base specification; we want at least one digit
1849 if (s
.empty ||
digit(s
.front
, base
) < 0) throw exBadInt
;
1852 // we already know that the next char is valid
1853 bool needDigit
= false;
1856 int d
= digit(ch
, base
);
1858 if (needDigit
) throw exBadInt
;
1859 if (ch
!= '_') break;
1862 // funny overflow checks
1864 if ((num
*= base
) < onum
) throw exIntOverflow
;
1865 if ((num
+= d
) < onum
) throw exIntOverflow
;
1870 if (needDigit
) throw exBadInt
;
1871 // check underflow and overflow
1872 static if (isSigned
!T
) {
1873 long n
= cast(long)num
;
1875 // special case: negative 0x8000_0000_0000_0000uL is ok
1876 if (num
> 0x8000_0000_0000_0000uL) throw exIntOverflow
;
1877 if (num
!= 0x8000_0000_0000_0000uL) n
= -(n
);
1879 if (num
>= 0x8000_0000_0000_0000uL) throw exIntOverflow
;
1881 if (n
< T
.min || n
> T
.max
) throw exIntOverflow
;
1884 if (num
< T
.min || num
> T
.max
) throw exIntOverflow
;
1891 * parser checks for overflows and understands different integer bases (0x, 0b, 0o, 0d).
1892 * parser skips leading spaces. stops on first non-numeric char.
1896 * s = input range; will be modified
1902 * ConvException or ConvOverflowException
1904 public static T
parseNum(T
, TS
) (ref TS s
) if (isSomeChar
!(ElementType
!TS
) && (isIntegral
!T || isFloatingPoint
!T
) && !is(T
== enum)) {
1905 static if (isIntegral
!T
) {
1906 return parseInt
!T(s
);
1909 if (s
.front
> 32) break;
1912 import std
.conv
: stcparse
= parse
;
1913 return stcparse
!T(s
);
1917 public static bool checkHelp (scope ConString s
) {
1919 while (pos
< s
.length
&& s
.ptr
[pos
] <= 32) ++pos
;
1920 if (pos
== s
.length || s
.ptr
[pos
] != '?') return false;
1922 while (pos
< s
.length
&& s
.ptr
[pos
] <= 32) ++pos
;
1923 return (pos
>= s
.length
);
1926 public static bool hasArgs (scope ConString s
) {
1928 while (pos
< s
.length
&& s
.ptr
[pos
] <= 32) ++pos
;
1929 return (pos
< s
.length
);
1932 private static bool isBadStrChar() (char ch
) {
1933 pragma(inline
, true);
1934 return (ch
< ' ' || ch
== '\\' || ch
== '"' || ch
== '\'' || ch
== '#' || ch
== ';' || ch
> 126);
1937 public static void writeQuotedString(bool forceQuote
=true) (scope ConString s
) { quoteStringDG
!forceQuote(s
, delegate (scope ConString s
) { conwriter(s
); }); }
1939 public static void quoteStringDG(bool forceQuote
=false) (scope ConString s
, scope void delegate (scope ConString
) dg
) {
1940 if (dg
is null) return;
1941 static immutable string hexd
= "0123456789abcdef";
1942 static bool isBadStrChar() (char ch
) {
1943 pragma(inline
, true);
1944 return (ch
< ' ' || ch
== '\\' || ch
== '"' || ch
== '\'' || ch
== '#' || ch
== ';' || ch
> 126);
1947 if (s
.length
== 0) { dg(`""`); return; }
1948 static if (!forceQuote
) {
1949 bool needQuote
= false;
1950 foreach (char ch
; s
) if (ch
== ' ' ||
isBadStrChar(ch
)) { needQuote
= true; break; }
1951 if (!needQuote
) { dg(s
); return; }
1955 while (pos
< s
.length
) {
1957 while (end
< s
.length
&& !isBadStrChar(s
.ptr
[end
])) ++end
;
1958 if (end
> pos
) dg(s
[pos
..end
]);
1960 if (pos
>= s
.length
) break;
1962 switch (s
.ptr
[pos
++]) {
1963 case '"': case '\'': case '\\': dg(s
.ptr
[pos
-1..pos
]); break;
1964 case '\x00': dg("0"); break;
1965 case '\a': dg("a"); break;
1966 case '\b': dg("b"); break;
1967 case '\x1b': dg("e"); break;
1968 case '\f': dg("f"); break;
1969 case '\n': dg("n"); break;
1970 case '\r': dg("r"); break;
1971 case '\t': dg("t"); break;
1972 case '\v': dg("c"); break;
1974 ubyte c
= cast(ubyte)(s
.ptr
[pos
-1]);
1976 dg(hexd
[c
>>4..(c
>>4)+1]);
1977 dg(hexd
[c
&0x0f..(c
&0x0f)+1]);
1986 version(contest_parser
) unittest {
1987 auto cc
= new ConCommand("!");
1988 string s
= "this is 'a test' string \"you\tknow\" ";
1989 auto sc
= cast(ConString
)s
;
1991 auto w
= cc
.getWord(sc
);
1992 assert(w
== "this");
1995 auto w
= cc
.getWord(sc
);
1999 auto w
= cc
.getWord(sc
);
2000 assert(w
== "a test");
2003 auto w
= cc
.getWord(sc
);
2004 assert(w
== "string");
2007 auto w
= cc
.getWord(sc
);
2008 assert(w
== "you\tknow");
2011 auto w
= cc
.getWord(sc
);
2013 assert(sc
.length
== 0);
2016 import std
.conv
: ConvException
, ConvOverflowException
;
2017 import std
.exception
;
2018 import std
.math
: abs
;
2020 void testnum(T
) (string s
, T res
, int line
=__LINE__
) {
2021 import std
.string
: format
;
2024 import std
.utf
: byCodeUnit
;
2025 auto ss
= s
.byCodeUnit
;
2026 auto v
= ConCommand
.parseNum
!T(ss
);
2027 while (!ss
.empty
&& ss
.front
<= 32) ss
.popFront();
2028 if (!ss
.empty
) throw new ConvException("shit happens!");
2029 static assert(is(typeof(v
) == T
));
2030 static if (isIntegral
!T
) ok
= (v
== res
); else ok
= (abs(v
-res
) < T
.epsilon
);
2031 } catch (ConvException e
) {
2032 assert(0, format("unexpected exception thrown, called from line %s", line
));
2034 if (!ok
) assert(0, format("assertion failure, called from line %s", line
));
2037 void testbadnum(T
) (string s
, int line
=__LINE__
) {
2038 import std
.string
: format
;
2040 import std
.utf
: byCodeUnit
;
2041 auto ss
= s
.byCodeUnit
;
2042 auto v
= ConCommand
.parseNum
!T(ss
);
2043 while (!ss
.empty
&& ss
.front
<= 32) ss
.popFront();
2044 if (!ss
.empty
) throw new ConvException("shit happens!");
2045 } catch (ConvException e
) {
2048 assert(0, format("exception not thrown, called from line %s", line
));
2051 testnum
!int(" -42", -42);
2052 testnum
!int(" +42", 42);
2053 testnum
!int(" -4_2", -42);
2054 testnum
!int(" +4_2", 42);
2055 testnum
!int(" -0d42", -42);
2056 testnum
!int(" +0d42", 42);
2057 testnum
!int("0x2a", 42);
2058 testnum
!int("-0x2a", -42);
2059 testnum
!int("0o52", 42);
2060 testnum
!int("-0o52", -42);
2061 testnum
!int("0b00101010", 42);
2062 testnum
!int("-0b00101010", -42);
2063 testnum
!ulong("+9223372036854775808", 9223372036854775808uL);
2064 testnum
!long("9223372036854775807", 9223372036854775807);
2065 testnum
!long("-9223372036854775808", -9223372036854775808uL); // uL to workaround issue #13606
2066 testnum
!ulong("+0x8000_0000_0000_0000", 9223372036854775808uL);
2067 testnum
!long("-0x8000_0000_0000_0000", -9223372036854775808uL); // uL to workaround issue #13606
2068 testbadnum
!long("9223372036854775808");
2069 testbadnum
!int("_42");
2070 testbadnum
!int("42_");
2071 testbadnum
!int("42_ ");
2072 testbadnum
!int("4__2");
2073 testbadnum
!int("0x_2a");
2074 testbadnum
!int("-0x_2a");
2075 testbadnum
!int("_0x2a");
2076 testbadnum
!int("-_0x2a");
2077 testbadnum
!int("_00x2a");
2078 testbadnum
!int("-_00x2a");
2079 testbadnum
!int(" +0x");
2081 testnum
!int("666", 666);
2082 testnum
!int("+666", 666);
2083 testnum
!int("-666", -666);
2085 testbadnum
!int("+");
2086 testbadnum
!int("-");
2087 testbadnum
!int("5a");
2089 testbadnum
!int("5.0");
2090 testbadnum
!int("5e+2");
2092 testnum
!uint("666", 666);
2093 testnum
!uint("+666", 666);
2094 testbadnum
!uint("-666");
2096 testnum
!int("0x29a", 666);
2097 testnum
!int("0X29A", 666);
2098 testnum
!int("-0x29a", -666);
2099 testnum
!int("-0X29A", -666);
2100 testnum
!int("0b100", 4);
2101 testnum
!int("0B100", 4);
2102 testnum
!int("-0b100", -4);
2103 testnum
!int("-0B100", -4);
2104 testnum
!int("0o666", 438);
2105 testnum
!int("0O666", 438);
2106 testnum
!int("-0o666", -438);
2107 testnum
!int("-0O666", -438);
2108 testnum
!int("0d666", 666);
2109 testnum
!int("0D666", 666);
2110 testnum
!int("-0d666", -666);
2111 testnum
!int("-0D666", -666);
2113 testnum
!byte("-0x7f", -127);
2114 testnum
!byte("-0x80", -128);
2115 testbadnum
!byte("0x80");
2116 testbadnum
!byte("-0x81");
2118 testbadnum
!uint("1a");
2119 testbadnum
!uint("0x1g");
2120 testbadnum
!uint("0b12");
2121 testbadnum
!uint("0o78");
2122 testbadnum
!uint("0d1f");
2124 testbadnum
!int("0x_2__9_a__");
2125 testbadnum
!uint("0x_");
2127 testnum
!ulong("0x8000000000000000", 0x8000000000000000UL
);
2128 testnum
!long("-0x8000000000000000", -0x8000000000000000uL
);
2129 testbadnum
!long("0x8000000000000000");
2130 testbadnum
!ulong("0x80000000000000001");
2131 testbadnum
!long("-0x8000000000000001");
2133 testbadnum
!float("-0O666");
2134 testnum
!float("0x666p0", 1638.0f);
2135 testnum
!float("-0x666p0", -1638.0f);
2136 testnum
!double("+1.1e+2", 110.0);
2137 testnum
!double("2.4", 2.4);
2138 testnum
!double("1_2.4", 12.4);
2139 testnum
!float("42666e-3", 42.666f);
2140 testnum
!float(" 4.2 ", 4.2);
2142 conwriteln("console: parser test passed");
2146 // ////////////////////////////////////////////////////////////////////////// //
2148 public final class ConAlias
{
2150 public import std
.conv
: ConvOverflowException
;
2151 __gshared ConvOverflowException exAliasTooBig
;
2152 __gshared ConvOverflowException exAliasTooDeep
;
2154 shared static this () {
2155 exAliasTooBig
= new ConvOverflowException("alias text too big");
2156 exAliasTooDeep
= new ConvOverflowException("alias expansion too deep");
2163 this (const(char)[] atext
) @nogc {
2168 import core
.stdc
.stdlib
: free
;
2169 if (textmem
!= 0) free(cast(void*)textmem
);
2172 const(char)[] get () nothrow @trusted @nogc { pragma(inline
, true); return (textmem
!= 0 ?
(cast(char*)textmem
)[0..textsize
] : null); }
2174 void set (const(char)[] atext
) @nogc {
2175 if (atext
.length
> 65535) throw exAliasTooBig
;
2177 if (atext
.length
> allocsize
) {
2178 import core
.stdc
.stdlib
: realloc
;
2180 if (atext
.length
<= 4096) newsz
= 4096;
2181 else if (atext
.length
<= 8192) newsz
= 8192;
2182 else if (atext
.length
<= 16384) newsz
= 16384;
2183 else if (atext
.length
<= 32768) newsz
= 32768;
2185 assert(newsz
>= atext
.length
);
2186 auto newmem
= cast(usize
)realloc(cast(void*)textmem
, newsz
);
2187 if (newmem
== 0) assert(0, "out of memory for alias"); //FIXME
2191 assert(allocsize
>= atext
.length
);
2192 textsize
= cast(uint)atext
.length
;
2193 (cast(char*)textmem
)[0..textsize
] = atext
;
2198 // ////////////////////////////////////////////////////////////////////////// //
2199 /// convar attributes
2200 public enum ConVarAttr
: uint {
2202 Archive
= 1U<<0, /// save on change (saving must be done by library user)
2203 Hex
= 1U<<1, /// dump this variable as hex value (valid for integrals)
2205 User
= 1U<<30, /// user-created
2206 ReadOnly
= 1U<<31, /// can't be changed with console command (direct change is still possible)
2210 /// variable of some type
2211 public class ConVarBase
: ConCommand
{
2216 alias PreChangeHookCB
= bool delegate (ConVarBase self
, ConString newval
); /// prototype for "before value change hook"; return `false` to abort; `newval` is not parsed
2217 alias PostChangeHookCB
= void delegate (ConVarBase self
, ConString newval
); /// prototype for "after value change hook"; `newval` is not parsed
2220 PreChangeHookCB hookBeforeChange
;
2221 PostChangeHookCB hookAfterChange
;
2224 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); } ///
2226 /// replaces current attributes (can't set `ConVarAttr.User` w/o !true)
2227 final void setAttrs(bool any
=false) (const(ConVarAttr
)[] attrs
...) pure nothrow @safe @nogc {
2229 foreach (const ConVarAttr a
; attrs
) {
2231 if (a
== ConVarAttr
.User
) continue;
2237 final ConVarBase
setBeforeHook (PreChangeHookCB cb
) { hookBeforeChange
= cb
; return this; } /// this can be chained
2238 final ConVarBase
setAfterHook (PostChangeHookCB cb
) { hookAfterChange
= cb
; return this; } /// this can be chained
2240 @property pure nothrow @safe @nogc final {
2241 PreChangeHookCB
beforeHook () { return hookBeforeChange
; } ///
2242 PostChangeHookCB
afterHook () { return hookAfterChange
; } ///
2244 void beforeHook (PreChangeHookCB cb
) { hookBeforeChange
= cb
; } ///
2245 void afterHook (PostChangeHookCB cb
) { hookAfterChange
= cb
; } ///
2247 /// attributes (see ConVarAttr enum)
2248 uint attrs () const { pragma(inline
, true); return mAttrs
; }
2249 /// replaces current attributes (can't set/remove `ConVarAttr.User` w/o !true)
2250 void attrs(bool any
=false) (uint v
) {
2251 pragma(inline
, true);
2252 static if (any
) mAttrs
= v
; else mAttrs
= v
&~(ConVarAttr
.User
);
2255 bool attrArchive () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.Archive
) != 0); } ///
2256 bool attrNoArchive () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.Archive
) == 0); } ///
2258 void attrArchive (bool v
) { pragma(inline
, true); if (v
) mAttrs |
= ConVarAttr
.Archive
; else mAttrs
&= ~ConVarAttr
.Archive
; } ///
2259 void attrNoArchive (bool v
) { pragma(inline
, true); if (!v
) mAttrs |
= ConVarAttr
.Archive
; else mAttrs
&= ~ConVarAttr
.Archive
; } ///
2261 bool attrHexDump () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.Hex
) != 0); } ///
2263 bool attrReadOnly () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.ReadOnly
) != 0); } ///
2264 void attrReadOnly (bool v
) { pragma(inline
, true); if (v
) mAttrs |
= ConVarAttr
.ReadOnly
; else mAttrs
&= ~ConVarAttr
.ReadOnly
; } ///
2266 bool attrUser () const { pragma(inline
, true); return ((mAttrs
&ConVarAttr
.User
) != 0); } ///
2269 abstract void printValue (); /// print variable value to console
2270 abstract bool isString () const pure nothrow @nogc; /// is this string variable?
2271 abstract ConString
strval () /*nothrow @nogc*/; /// get string value (for any var)
2273 /// get variable value, converted to the given type (if it is possible).
2274 @property T
value(T
) () /*nothrow @nogc*/ {
2275 pragma(inline
, true);
2276 static if (is(T
== enum)) {
2277 return cast(T
)getIntValue
;
2278 } else static if (is(T
: ulong)) {
2279 // integer, xchar, boolean
2280 return cast(T
)getIntValue
;
2281 } else static if (is(T
: double)) {
2283 return cast(T
)getDoubleValue
;
2284 } else static if (is(T
: ConString
)) {
2286 static if (is(T
== string
)) return strval
.idup
;
2287 else static if (is(T
== char[])) return strval
.dup
;
2295 /// set variable value, converted from the given type (if it is possible).
2296 /// ReadOnly is ignored, no hooks will be called.
2297 @property void value(T
) (T val
) /*nothrow*/ {
2298 pragma(inline
, true);
2299 static if (is(T
: ulong)) {
2300 // integer, xchar, boolean
2301 setIntValue(cast(ulong)val
, isSigned
!T
);
2302 } else static if (is(T
: double)) {
2304 setDoubleValue(cast(double)val
);
2305 } else static if (is(T
: ConString
)) {
2306 static if (is(T
== string
)) setStrValue(val
); else setCCharValue(val
);
2308 static assert(0, "invalid type");
2313 abstract ulong getIntValue () /*nothrow @nogc*/;
2314 abstract double getDoubleValue () /*nothrow @nogc*/;
2316 abstract void setIntValue (ulong v
, bool signed
) /*nothrow @nogc*/;
2317 abstract void setDoubleValue (double v
) /*nothrow @nogc*/;
2318 abstract void setStrValue (string v
) /*nothrow*/;
2319 abstract void setCCharValue (ConString v
) /*nothrow*/;
2323 // ////////////////////////////////////////////////////////////////////////// //
2324 /// console will use this to register console variables
2325 final class ConVar(T
) : ConVarBase
{
2327 enum useAtomic
= is(T
== shared);
2329 static if (isIntegral
!TT
) {
2332 } else static if (isFloatingPoint
!TT
) {
2336 static if (!is(TT
: ConString
)) {
2339 char[256] tvbuf
; // temp value
2342 TT
delegate (ConVarBase self
) cvGetter
;
2343 void delegate (ConVarBase self
, TT nv
) cvSetter
;
2345 void delegate (ConVarBase self
, TT pv
, TT nv
) hookAfterChangeVV
;
2347 this (T
* avptr
, string aname
, string ahelp
=null) {
2349 super(aname
, ahelp
);
2352 static if (isIntegral
!TT || isFloatingPoint
!TT
) {
2353 this (T
* avptr
, TT aminv
, TT amaxv
, string aname
, string ahelp
=null) {
2357 super(aname
, ahelp
);
2361 /// this method will respect `ReadOnly` flag, and will call before/after hooks.
2362 override void exec (ConString cmdline
) {
2363 if (checkHelp(cmdline
)) { showHelp
; return; }
2364 if (!hasArgs(cmdline
)) { printValue
; return; }
2365 if (attrReadOnly
) return; // can't change read-only var with console commands
2366 static if ((is(TT
== bool) || isIntegral
!TT || isFloatingPoint
!TT
) && !is(TT
== enum)) {
2367 while (cmdline
.length
&& cmdline
[0] <= 32) cmdline
= cmdline
[1..$];
2368 while (cmdline
.length
&& cmdline
[$-1] <= 32) cmdline
= cmdline
[0..$-1];
2369 if (cmdline
== "toggle") {
2370 if (hookBeforeChange
!is null) { if (!hookBeforeChange(this, cmdline
)) return; }
2371 auto prval
= getv();
2372 if (cvSetter
!is null) {
2373 cvSetter(this, !prval
);
2375 static if (useAtomic
) {
2377 atomicStore(*vptr
, !prval
);
2382 if (hookAfterChangeVV
!is null) hookAfterChangeVV(this, prval
, !prval
);
2383 if (hookAfterChange
!is null) hookAfterChange(this, cmdline
);
2387 auto newvals
= cmdline
;
2388 TT val
= parseType
!TT(/*ref*/ cmdline
);
2389 if (hasArgs(cmdline
)) throw exTooManyArgs
;
2390 static if (isIntegral
!TT
) {
2391 if (val
< minv
) val
= minv
;
2392 if (val
> maxv
) val
= maxv
;
2394 if (hookBeforeChange
!is null) { if (!hookBeforeChange(this, newvals
)) return; }
2396 if (hookAfterChangeVV
!is null) oval
= getv();
2397 if (cvSetter
!is null) {
2398 cvSetter(this, val
);
2400 static if (useAtomic
) {
2402 atomicStore(*vptr
, val
);
2407 if (hookAfterChangeVV
!is null) hookAfterChangeVV(this, oval
, val
);
2408 if (hookAfterChange
!is null) hookAfterChange(this, newvals
);
2411 override bool isString () const pure nothrow @nogc {
2412 static if (is(TT
: ConString
)) return true; else return false;
2415 final private TT
getv() () /*nothrow @nogc*/ {
2416 pragma(inline
, true);
2418 if (cvGetter
!is null) {
2419 return cvGetter(this);
2421 static if (useAtomic
) return atomicLoad(*vptr
); else return *vptr
;
2425 override ConString
strval () /*nothrow @nogc*/ {
2426 //conwriteln("*** strval for '", name, "'");
2427 import core
.stdc
.stdio
: snprintf
;
2428 static if (is(TT
== enum)) {
2430 foreach (string mname
; __traits(allMembers
, TT
)) {
2431 if (__traits(getMember
, TT
, mname
) == v
) return mname
;
2434 } else static if (is(T
: ConString
)) {
2436 } else static if (is(XUQQ
!T
== bool)) {
2437 return (getv() ?
"tan" : "ona");
2438 } else static if (isIntegral
!T
) {
2439 static if (isSigned
!T
) {
2440 auto len
= snprintf(vbuf
.ptr
, vbuf
.length
, "%lld", cast(long)(getv()));
2442 auto len
= snprintf(vbuf
.ptr
, vbuf
.length
, (attrHexDump ?
"0x%06llx".ptr
: "%llu".ptr
), cast(ulong)(getv()));
2444 return (len
>= 0 ? vbuf
[0..len
] : "?");
2445 } else static if (isFloatingPoint
!T
) {
2446 auto len
= snprintf(vbuf
.ptr
, vbuf
.length
, "%g", cast(double)(getv()));
2447 return (len
>= 0 ? vbuf
[0..len
] : "?");
2448 } else static if (is(XUQQ
!T
== char)) {
2449 vbuf
.ptr
[0] = cast(char)getv();
2452 static assert(0, "can't get string value of convar with type '"~T
.stringof
~"'");
2456 protected override ulong getIntValue () /*nothrow @nogc*/ {
2457 static if (is(T
: ulong) ||
is(T
: double)) return cast(ulong)(getv()); else return ulong.init
;
2460 protected override double getDoubleValue () /*nothrow @nogc*/ {
2461 static if (is(T
: double) ||
is(T
: ulong)) return cast(double)(getv()); else return double.init
;
2464 private template PutVMx(string val
) {
2465 static if (useAtomic
) {
2466 enum PutVMx
= "{ import core.atomic; if (cvSetter !is null) cvSetter(this, "~val
~"); else atomicStore(*vptr, "~val
~"); }";
2468 enum PutVMx
= "{ if (cvSetter !is null) cvSetter(this, "~val
~"); else *vptr = "~val
~"; }";
2472 protected override void setIntValue (ulong v
, bool signed
) /*nothrow @nogc*/ {
2474 static if (is(XUQQ
!T
== enum)) {
2475 foreach (string mname
; __traits(allMembers
, TT
)) {
2476 if (__traits(getMember
, TT
, mname
) == v
) {
2477 mixin(PutVMx
!"cast(T)v");
2482 conwriteln("invalid enum value '", v
, "' for variable '", name
, "'");
2483 } else static if (is(T
: ulong) ||
is(T
: double)) {
2484 mixin(PutVMx
!"cast(T)v");
2485 } else static if (is(T
: ConString
)) {
2486 import core
.stdc
.stdio
: snprintf
;
2487 auto len
= snprintf(tvbuf
.ptr
, tvbuf
.length
, (signed ?
"%lld" : "%llu"), v
);
2488 static if (is(T
== string
)) mixin(PutVMx
!"cast(string)(tvbuf[0..len])"); // not really safe, but...
2489 else static if (is(T
== ConString
)) mixin(PutVMx
!"cast(ConString)(tvbuf[0..len])");
2490 else static if (is(T
== char[])) mixin(PutVMx
!"tvbuf[0..len]");
2494 protected override void setDoubleValue (double v
) /*nothrow @nogc*/ {
2496 static if (is(XUQQ
!T
== enum)) {
2497 foreach (string mname
; __traits(allMembers
, TT
)) {
2498 if (__traits(getMember
, TT
, mname
) == v
) {
2499 mixin(PutVMx
!"cast(T)v");
2504 conwriteln("invalid enum value '", v
, "' for variable '", name
, "'");
2505 } else static if (is(T
: ulong) ||
is(T
: double)) {
2506 mixin(PutVMx
!"cast(T)v");
2507 } else static if (is(T
: ConString
)) {
2508 import core
.stdc
.stdio
: snprintf
;
2509 auto len
= snprintf(tvbuf
.ptr
, tvbuf
.length
, "%g", v
);
2510 static if (is(T
== string
)) mixin(PutVMx
!"cast(string)(tvbuf[0..len])"); // not really safe, but...
2511 else static if (is(T
== ConString
)) mixin(PutVMx
!"cast(ConString)(tvbuf[0..len])");
2512 else static if (is(T
== char[])) mixin(PutVMx
!"tvbuf[0..len]");
2516 protected override void setStrValue (string v
) /*nothrow*/ {
2518 static if (is(XUQQ
!T
== enum)) {
2521 auto vv
= ConCommand
.parseType
!T(ss
);
2522 mixin(PutVMx
!"cast(T)vv");
2523 } catch (Exception
) {
2524 conwriteln("invalid enum value '", v
, "' for variable '", name
, "'");
2526 } else static if (is(T
== string
) ||
is(T
== ConString
)) {
2527 mixin(PutVMx
!"cast(T)v");
2528 } else static if (is(T
== char[])) {
2529 mixin(PutVMx
!"v.dup");
2533 protected override void setCCharValue (ConString v
) /*nothrow*/ {
2535 static if (is(XUQQ
!T
== enum)) {
2538 auto vv
= ConCommand
.parseType
!T(ss
);
2539 mixin(PutVMx
!"cast(T)vv");
2540 } catch (Exception
) {
2541 conwriteln("invalid enum value '", v
, "' for variable '", name
, "'");
2544 else static if (is(T
== string
)) mixin(PutVMx
!"v.idup");
2545 else static if (is(T
== ConString
)) mixin(PutVMx
!"v");
2546 else static if (is(T
== char[])) mixin(PutVMx
!"v.dup");
2549 override void printValue () {
2550 static if (is(T
== enum)) {
2552 foreach (string mname
; __traits(allMembers
, TT
)) {
2553 if (__traits(getMember
, TT
, mname
) == vx
) {
2556 //writeQuotedString(mname);
2563 conwriteln(name
, " ", cast(ulong)getv());
2564 } else static if (is(T
: ConString
)) {
2567 writeQuotedString(getv());
2569 } else static if (is(XUQQ
!T
== char)) {
2572 char[1] st
= getv();
2573 if (st
[0] <= ' ' || st
[0] == 127 || st
[0] == '"' || st
[0] == '\\') {
2574 writeQuotedString(st
[]);
2581 } else static if (is(T
== bool)) {
2582 conwriteln(name
, " ", (getv() ?
"tan" : "ona"));
2584 conwriteln(name
, " ", strval
);
2591 version(contest_vars
) unittest {
2592 __gshared
int vi
= 42;
2593 __gshared string vs
= "str";
2594 __gshared
bool vb
= true;
2595 auto cvi
= new ConVar
!int(&vi
, "vi", "integer variable");
2596 auto cvs
= new ConVar
!string(&vs
, "vs", "string variable");
2597 auto cvb
= new ConVar
!bool(&vb
, "vb", "bool variable");
2605 cvs
.exec("'fo\to'");
2608 conwriteln("vi=", vi
);
2609 conwriteln("vs=[", vs
, "]");
2619 // don't use std.algo
2620 private void heapsort(alias lessfn
, T
) (T
[] arr
) {
2621 auto count
= arr
.length
;
2622 if (count
< 2) return; // nothing to do
2624 void siftDown(T
) (T start
, T end
) {
2627 auto child
= 2*root
+1; // left child
2628 if (child
> end
) break;
2630 if (lessfn(arr
.ptr
[swap
], arr
.ptr
[child
])) swap
= child
;
2631 if (child
+1 <= end
&& lessfn(arr
.ptr
[swap
], arr
.ptr
[child
+1])) swap
= child
+1;
2632 if (swap
== root
) break;
2633 auto tmp
= arr
.ptr
[swap
];
2634 arr
.ptr
[swap
] = arr
.ptr
[root
];
2635 arr
.ptr
[root
] = tmp
;
2642 auto start
= (end
-1)/2; // parent; always safe, as our array has at least two items
2644 siftDown(start
, end
);
2645 if (start
-- == 0) break; // as `start` cannot be negative, use this condition
2650 auto tmp
= arr
.ptr
[0];
2651 arr
.ptr
[0] = arr
.ptr
[end
];
2660 private void conUnsafeArrayAppend(T
) (ref T
[] arr
, auto ref T v
) /*nothrow*/ {
2661 if (arr
.length
>= int.max
/2) assert(0, "too many elements in array");
2662 auto optr
= arr
.ptr
;
2664 if (arr
.ptr
!is optr
) {
2665 import core
.memory
: GC
;
2667 if (optr
is GC
.addrOf(optr
)) GC
.setAttr(optr
, GC
.BlkAttr
.NO_INTERIOR
);
2672 private void addName (string name
) {
2673 //{ import core.stdc.stdio; printf("%.*s\n", cast(uint)name.length, name.ptr); }
2675 static bool strLessCI (const(char)[] s0
, const(char)[] s1
) pure nothrow @trusted @nogc {
2676 auto slen
= s0
.length
;
2677 if (slen
> s1
.length
) slen
= s1
.length
;
2679 foreach (immutable idx
, char c0
; s0
[0..slen
]) {
2680 if (c0
>= 'A' && c0
<= 'Z') c0
+= 32; // poor man's tolower()
2682 if (c1
>= 'A' && c1
<= 'Z') c1
+= 32; // poor man's tolower()
2683 if (c0
< c1
) return true;
2684 if (c0
> c1
) return false;
2686 if (s0
.length
< s1
.length
) return true;
2687 if (s0
.length
> s1
.length
) return false;
2691 if (name
.length
== 0) return;
2692 if (name
!in cmdlist
) {
2693 //cmdlistSorted ~= name;
2694 cmdlistSorted
.conUnsafeArrayAppend(name
);
2695 heapsort
!strLessCI(cmdlistSorted
);
2700 // ////////////////////////////////////////////////////////////////////////// //
2703 void concmdRangeCompleter(IR
, bool casesens
=true) (ConCommand self
, auto ref IR rng
) if (isForwardRange
!IR
&& is(ElementEncodingType
!IR
: const(char)[])) {
2704 if (rng
.empty
) return; // oops
2705 auto cs
= conInputBuffer
[0..conInputBufferCurX
];
2706 ConCommand
.getWord(cs
); // skip command
2707 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2708 if (cs
.length
== 0) {
2709 conwriteln(self
.name
, ":");
2710 foreach (const(char)[] mname
; rng
) conwriteln(" ", mname
);
2712 if (cs
[0] == '"' || cs
[0] == '\'') return; // alas
2713 ConString pfx
= ConCommand
.getWord(cs
);
2714 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2715 if (cs
.length
) return; // alas
2716 const(char)[] bestPfx
;
2718 foreach (const(char)[] mname
; rng
.save
) {
2719 if (mname
.length
>= pfx
.length
&& strEquCI(mname
[0..pfx
.length
], pfx
)) {
2723 //if (mname.length < bestPfx.length) bestPfx = bestPfx[0..mname.length];
2725 while (pos
< bestPfx
.length
&& pos
< mname
.length
) {
2726 char c0
= bestPfx
[pos
];
2727 char c1
= mname
[pos
];
2728 static if (!casesens
) {
2729 if (c0
>= 'A' && c0
<= 'Z') c0
+= 32; // poor man's tolower()
2730 if (c1
>= 'A' && c1
<= 'Z') c1
+= 32; // poor man's tolower()
2732 if (c0
!= c1
) break;
2735 if (pos
< bestPfx
.length
) bestPfx
= bestPfx
[0..pos
];
2740 if (count
== 0 || bestPfx
.length
< pfx
.length
) { conwriteln(self
.name
, ": ???"); return; }
2741 foreach (char ch
; bestPfx
[pfx
.length
..$]) conAddInputChar(ch
);
2743 conAddInputChar(' ');
2745 conwriteln(self
.name
, ":");
2746 foreach (const(char)[] mname
; rng
) {
2747 if (mname
.length
>= bestPfx
.length
&& mname
[0..bestPfx
.length
] == bestPfx
) {
2748 conwriteln(" ", mname
);
2756 // ////////////////////////////////////////////////////////////////////////// //
2757 /// helper function for [cmdconSetFileCompleter]
2758 public string
[] cmdconFilesList (string pfx
, scope bool delegate (ConString name
) nothrow isGood
=null) nothrow {
2759 static bool startsWith (const(char)[] str, const(char)[] prefix
) pure nothrow @trusted @nogc {
2760 if (prefix
.length
== 0) return true;
2761 if (prefix
.length
> str.length
) return false;
2762 return (str[0..prefix
.length
] == prefix
);
2769 if (pfx
.length
== 0) {
2772 if (pfx
[$-1] == '/') {
2780 string
xxname (string fn
, bool asDir
=false) {
2781 if (fn
== "/") return fn
;
2783 if (fn
== ".") return "./";
2785 if (startsWith(fn
, "./")) fn
= fn
[2..$];
2786 if (asDir
) fn
~= "/";
2790 foreach (DirEntry
de; dirEntries(path
, SpanMode
.shallow
)) {
2791 if (de.baseName
.length
== 0) continue;
2792 if (de.baseName
[0] == '.') continue;
2795 if (isGood
is null ||
isGood(de.name
)) {
2796 if (pfx
.length
== 0 ||
startsWith(de.baseName
, pfx
)) res
~= xxname(de.name
);
2798 } else if (de.isDir
) {
2799 if (pfx
.length
== 0 ||
startsWith(de.baseName
, pfx
)) res
~= xxname(de.name
, true);
2801 } catch (Exception e
) {} // sorry
2803 } catch (Exception e
) {} // sorry
2804 import std
.algorithm
: sort
;
2805 res
.sort
!((string a
, string b
) {
2806 if (a
[$-1] == '/') {
2807 if (b
[$-1] != '/') return true;
2810 if (b
[$-1] == '/') return false;
2817 /// if `getCurrentFile` is not null, and sole "!" is given as argument, current file will be put
2818 public void cmdconSetFileCompleter (ConCommand cmd
, string
delegate () nothrow getCurrentFile
=null, bool delegate (ConString name
) nothrow isGood
=null) {
2819 if (cmd
is null) return;
2820 cmd
.completer
= delegate (ConCommand self
) {
2821 auto cs
= conInputBuffer
[0..conInputBufferCurX
];
2822 ConCommand
.getWord(cs
); // skip command
2823 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2824 // put currently loaded file to buffer
2825 if (cs
== "!" && getCurrentFile
!is null) {
2826 string cfn
= getCurrentFile();
2828 conAddInputChar(ConInputChar
.Backspace
); // remove "!"
2829 foreach (char ch
; cfn
) conAddInputChar(ch
);
2833 string
[] list
= cmdconFilesList(cs
.idup
, isGood
);
2834 if (list
.length
== 0) {
2835 conwriteln(self
.name
, ": no matching files");
2838 auto lc
= conInputLastChange();
2839 self
.concmdRangeCompleter(list
);
2840 // if we ended with "/ ", remove space
2841 if (lc
!= conInputLastChange()) {
2842 cs
= conInputBuffer
[0..conInputBufferCurX
];
2843 if (cs
.length
> 2 && cs
[$-1] == ' ' && cs
[$-2] == '/') {
2844 conAddInputChar(ConInputChar
.Backspace
);
2851 // ////////////////////////////////////////////////////////////////////////// //
2852 private void enumComplete(T
) (ConCommand self
) if (is(T
== enum)) {
2853 auto cs
= conInputBuffer
[0..conInputBufferCurX
];
2854 ConCommand
.getWord(cs
); // skip command
2855 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2856 if (cs
.length
== 0) {
2857 conwriteln(self
.name
, ":");
2858 foreach (string mname
; __traits(allMembers
, T
)) conwriteln(" ", mname
);
2860 if (cs
[0] == '"' || cs
[0] == '\'') return; // alas
2861 ConString pfx
= ConCommand
.getWord(cs
);
2862 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2863 if (cs
.length
) return; // alas
2866 foreach (string mname
; __traits(allMembers
, T
)) {
2867 if (mname
.length
>= pfx
.length
&& strEquCI(mname
[0..pfx
.length
], pfx
)) {
2871 //if (mname.length < bestPfx.length) bestPfx = bestPfx[0..mname.length];
2873 while (pos
< bestPfx
.length
&& pos
< mname
.length
) {
2874 char c0
= bestPfx
[pos
];
2875 char c1
= mname
[pos
];
2876 if (c0
>= 'A' && c0
<= 'Z') c0
+= 32; // poor man's tolower()
2877 if (c1
>= 'A' && c1
<= 'Z') c1
+= 32; // poor man's tolower()
2878 if (c0
!= c1
) break;
2881 if (pos
< bestPfx
.length
) bestPfx
= bestPfx
[0..pos
];
2886 if (count
== 0 || bestPfx
.length
< pfx
.length
) { conwriteln(self
.name
, ": ???"); return; }
2887 foreach (char ch
; bestPfx
[pfx
.length
..$]) conAddInputChar(ch
);
2889 conAddInputChar(' ');
2891 conwriteln(self
.name
, ":");
2892 foreach (string mname
; __traits(allMembers
, T
)) {
2893 if (mname
.length
>= bestPfx
.length
&& mname
[0..bestPfx
.length
] == bestPfx
) {
2894 conwriteln(" ", mname
);
2902 private void boolComplete(T
) (ConCommand self
) if (is(T
== bool)) {
2903 auto cs
= conInputBuffer
[0..conInputBufferCurX
];
2904 ConCommand
.getWord(cs
); // skip command
2905 while (cs
.length
&& cs
[0] <= ' ') cs
= cs
[1..$];
2906 if (cs
.length
> 0) {
2907 enum Cmd
= "toggle";
2908 foreach (immutable idx
, char ch
; Cmd
) {
2909 if (idx
>= cs
.length
) break;
2910 if (cs
[idx
] != ch
) return; // alas
2912 if (cs
.length
> Cmd
.length
) return;
2913 foreach (char ch
; Cmd
[cs
.length
..$]) conAddInputChar(ch
);
2914 conAddInputChar(' ');
2916 conwriteln(" toggle?");
2921 /** register console variable with getter and setter.
2924 * aname = variable name
2928 * attrs = convar attributes (see `ConVarAttr`)
2930 public ConVarBase
conRegVar(T
) (string aname
, string ahelp
, T
delegate (ConVarBase self
) dgg
, void delegate (ConVarBase self
, T nv
) dgs
, const(ConVarAttr
)[] attrs
...)
2931 if (!is(T
== struct) && !is(T
== class))
2933 if (dgg
is null || dgs
is null) assert(0, "cmdcon: both getter and setter should be defined for convar");
2934 if (aname
.length
> 0) {
2936 auto cv
= new ConVar
!T(null, aname
, ahelp
);
2940 cmdlist
[aname
] = cv
;
2948 private enum RegVarMixin(string cvcreate
) =
2949 `static assert(isGoodVar!v, "console variable '"~v.stringof~"' must be shared or __gshared");`~
2950 `if (aname.length == 0) {`~
2951 `aname = (&v).stringof[2..$]; /*HACK*/`~
2952 ` foreach_reverse (immutable cidx, char ch; aname) if (ch == '.') { aname = aname[cidx+1..$]; break; }`~
2954 `if (aname.length > 0) {`~
2956 ` auto cv = `~cvcreate
~`;`~
2957 ` cv.setAttrs(attrs);`~
2958 ` alias UT = XUQQ!(typeof(v));`~
2959 ` static if (is(UT == enum)) {`~
2960 ` import std.functional : toDelegate;`~
2961 ` cv.argcomplete = toDelegate(&enumComplete!UT);`~
2962 ` } else static if (is(UT == bool)) {`~
2963 ` import std.functional : toDelegate;`~
2964 ` cv.argcomplete = toDelegate(&boolComplete!UT);`~
2966 ` cmdlist[aname] = cv;`~
2973 /** register integral console variable with bounded value.
2976 * v = variable symbol
2977 * aminv = minimum value
2978 * amaxv = maximum value
2979 * aname = variable name
2981 * attrs = convar attributes (see `ConVarAttr`)
2983 public ConVarBase
conRegVar(alias v
, T
) (T aminv
, T amaxv
, string aname
, string ahelp
, const(ConVarAttr
)[] attrs
...)
2984 if ((isIntegral
!(typeof(v
)) && isIntegral
!T
) ||
(isFloatingPoint
!(typeof(v
)) && (isIntegral
!T || isFloatingPoint
!T
)))
2986 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp)`);
2989 /** register integral console variable with old/new delegate.
2992 * v = variable symbol
2993 * aname = variable name
2995 * cdg = old/new change delegate
2996 * attrs = convar attributes (see `ConVarAttr`)
2998 ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, void delegate (ConVarBase self
, XUQQ
!(typeof(v
)) oldv
, XUQQ
!(typeof(v
)) newv
) cdg
, const(ConVarAttr
)[] attrs
...)
2999 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)) {
3000 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookAfterChangeVV = cdg`);
3003 /** register integral console variable with bounded value and old/new delegate.
3006 * v = variable symbol
3007 * aminv = minimum value
3008 * amaxv = maximum value
3009 * aname = variable name
3011 * cdg = old/new change delegate
3012 * attrs = convar attributes (see `ConVarAttr`)
3014 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
...)
3015 if (is(XUQQ
!(typeof(v
)) : long) ||
is(XUQQ
!(typeof(v
)) : double) && !is(XUQQ
!(typeof(v
)) == bool) && !is(XUQQ
!(typeof(v
)) == enum)) {
3016 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, amin, amax, aname, ahelp); cv.hookAfterChangeVV = cdg`);
3019 /** register integral console variable with bounded value.
3022 * v = variable symbol
3023 * aminv = minimum value
3024 * amaxv = maximum value
3025 * aname = variable name
3027 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3028 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3029 * attrs = convar attributes (see `ConVarAttr`)
3031 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
) {
3032 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3035 /** register integral console variable with bounded value.
3038 * v = variable symbol
3039 * aminv = minimum value
3040 * amaxv = maximum value
3041 * aname = variable name
3043 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3044 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3045 * attrs = convar attributes (see `ConVarAttr`)
3047 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
) {
3048 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3051 /** register integral console variable with bounded value.
3054 * v = variable symbol
3055 * aminv = minimum value
3056 * amaxv = maximum value
3057 * aname = variable name
3059 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3060 * attrs = convar attributes (see `ConVarAttr`)
3062 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
) {
3063 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookBeforeChange = bcb`);
3066 /** register integral console variable with bounded value.
3069 * v = variable symbol
3070 * aminv = minimum value
3071 * amaxv = maximum value
3072 * aname = variable name
3074 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3075 * attrs = convar attributes (see `ConVarAttr`)
3077 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
) {
3078 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookAfterChange = acb`);
3082 /** register console variable.
3085 * v = variable symbol
3086 * aname = variable name
3088 * attrs = convar attributes (see `ConVarAttr`)
3090 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3091 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp)`);
3094 /** register console variable.
3097 * v = variable symbol
3098 * aname = variable name
3100 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3101 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3102 * attrs = convar attributes (see `ConVarAttr`)
3104 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, ConVarBase
.PreChangeHookCB bcb
, ConVarBase
.PostChangeHookCB acb
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3105 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3108 /** register console variable.
3111 * v = variable symbol
3112 * aname = variable name
3114 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3115 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3116 * attrs = convar attributes (see `ConVarAttr`)
3118 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, ConVarBase
.PostChangeHookCB acb
, ConVarBase
.PreChangeHookCB bcb
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3119 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3122 /** register console variable.
3125 * v = variable symbol
3126 * aname = variable name
3128 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3129 * attrs = convar attributes (see `ConVarAttr`)
3131 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, ConVarBase
.PreChangeHookCB bcb
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3132 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookBeforeChange = bcb`);
3135 /** register console variable.
3138 * v = variable symbol
3139 * aname = variable name
3141 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3142 * attrs = convar attributes (see `ConVarAttr`)
3144 public ConVarBase
conRegVar(alias v
) (string aname
, string ahelp
, ConVarBase
.PostChangeHookCB acb
, const(ConVarAttr
)[] attrs
...) if (!isCallable
!(typeof(v
))) {
3145 mixin(RegVarMixin
!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookAfterChange = acb`);
3149 // ////////////////////////////////////////////////////////////////////////// //
3151 public class ConFuncBase
: ConCommand
{
3152 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); }
3156 /// use `conRegFunc!((ConFuncVA va) {...}, ...)` to switch off automatic argument parsing
3157 public struct ConFuncVA
{
3162 /** register console command.
3166 * aname = variable name
3169 public ConFuncBase
conRegFunc(alias fn
) (string aname
, string ahelp
) if (isCallable
!fn
) {
3170 // we have to make the class nested, so we can use `dg`, which keeps default args
3172 // hack for inline lambdas
3173 static if (is(typeof(&fn
))) {
3179 class ConFunc
: ConFuncBase
{
3180 this (string aname
, string ahelp
=null) { super(aname
, ahelp
); }
3182 override void exec (ConString cmdline
) {
3183 if (checkHelp(cmdline
)) { showHelp
; return; }
3185 static if (args
.length
== 0) {
3186 if (hasArgs(cmdline
)) {
3187 conwriteln("too many args for command '", name
, "'");
3191 } else static if (args
.length
== 1 && is(typeof(args
[0]) == ConFuncVA
)) {
3192 args
[0].cmdline
= cmdline
;
3195 alias defaultArguments
= ParameterDefaults
!fn
;
3196 //pragma(msg, "defs: ", defaultArguments);
3197 import std
.conv
: to
;
3198 ConString
[128] rest
; // to avoid allocations in most cases
3199 ConString
[] restdyn
; // will be used if necessary
3201 foreach (/*auto*/ idx
, ref arg
; args
) {
3202 // populate arguments, with user data if available,
3203 // default if not, and throw if no argument provided
3204 if (hasArgs(cmdline
)) {
3205 import std
.conv
: ConvException
;
3206 static if (is(typeof(arg
) == ConString
[])) {
3208 if (idx
!= args
.length
-1) {
3209 conwriteln("error parsing argument #", xidx
, " for command '", name
, "'");
3212 while (hasArgs(cmdline
)) {
3214 ConString cs
= parseType
!ConString(cmdline
);
3215 if (restpos
== rest
.length
) { restdyn
= rest
[]; ++restpos
; }
3216 if (restpos
< rest
.length
) rest
[restpos
++] = cs
; else restdyn
~= cs
;
3218 } catch (ConvException
) {
3219 conwriteln("error parsing argument #", xidx
, " for command '", name
, "'");
3223 arg
= (restpos
<= rest
.length ? rest
[0..restpos
] : restdyn
);
3226 arg
= parseType
!(typeof(arg
))(cmdline
);
3227 } catch (ConvException
) {
3228 conwriteln("error parsing argument #", idx
+1, " for command '", name
, "'");
3233 static if (!is(defaultArguments
[idx
] == void)) {
3234 arg
= defaultArguments
[idx
];
3236 conwriteln("required argument #", idx
+1, " for command '", name
, "' is missing");
3241 if (hasArgs(cmdline
)) {
3242 conwriteln("too many args for command '", name
, "'");
3245 //static if (is(ReturnType!dg == void))
3251 static if (is(typeof(&fn
))) {
3252 if (aname
.length
== 0) aname
= (&fn
).stringof
[2..$]; // HACK
3254 if (aname
.length
> 0) {
3255 auto cf
= new ConFunc(aname
, ahelp
);
3257 cmdlist
[aname
] = cf
;
3265 // ////////////////////////////////////////////////////////////////////////// //
3266 __gshared
/*ConCommand*/Object
[string
] cmdlist
; // ConCommand or ConAlias
3267 __gshared string
[] cmdlistSorted
;
3270 /** set argument completion delegate for command.
3272 * delegate will be called from `conAddInputChar()`.
3273 * delegate can use `conInputBuffer()` to get current input buffer,
3274 * `conInputBufferCurX()` to get cursor position, and
3275 * `conAddInputChar()` itself to put new chars into buffer.
3277 void conSetArgCompleter (ConString cmdname
, ConCommand
.ArgCompleteCB ac
) {
3278 if (auto cp
= cmdname
in cmdlist
) {
3279 if (auto cmd
= cast(ConCommand
)(*cp
)) cmd
.argcomplete
= ac
;
3284 // ////////////////////////////////////////////////////////////////////////// //
3285 // all following API is thread-unsafe, if the opposite is not written
3286 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)
3287 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)
3288 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)
3290 /// known console commands (funcs and vars) range (thread-unsafe)
3291 /// type: "all", "vars", "funcs"
3292 public auto conByCommand(string type
="all") () if (type
== "all" || type
== "vars" || type
== "funcs" || type
== "aliases" || type
== "archive") {
3293 static struct Range(string type
) {
3298 this (usize stidx
) {
3299 static if (type
== "all") {
3301 } else static if (type
== "vars") {
3302 while (stidx
< cmdlistSorted
.length
&& (cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[stidx
]]) is null) ++stidx
;
3304 } else static if (type
== "funcs") {
3305 while (stidx
< cmdlistSorted
.length
&& (cast(ConFuncBase
)cmdlist
[cmdlistSorted
.ptr
[stidx
]]) is null) ++stidx
;
3307 } else static if (type
== "aliases") {
3308 while (stidx
< cmdlistSorted
.length
&& (cast(ConAlias
)cmdlist
[cmdlistSorted
.ptr
[stidx
]]) is null) ++stidx
;
3310 } else static if (type
== "archive") {
3311 while (stidx
< cmdlistSorted
.length
) {
3312 if (auto v
= cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[stidx
]]) {
3313 if (v
.attrArchive
) break;
3319 static assert(0, "wtf?!");
3324 @property bool empty() () { pragma(inline
, true); return (idx
>= cmdlistSorted
.length
); }
3325 @property string
front() () { pragma(inline
, true); return (idx
< cmdlistSorted
.length ? cmdlistSorted
.ptr
[idx
] : null); }
3326 @property bool frontIsVar() () { pragma(inline
, true); return (idx
< cmdlistSorted
.length ?
(cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]] !is null) : false); }
3327 @property bool frontIsFunc() () { pragma(inline
, true); return (idx
< cmdlistSorted
.length ?
(cast(ConFuncBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]] !is null) : false); }
3328 @property bool frontIsAlias() () { pragma(inline
, true); return (idx
< cmdlistSorted
.length ?
(cast(ConAlias
)cmdlist
[cmdlistSorted
.ptr
[idx
]] !is null) : false); }
3330 if (idx
>= cmdlistSorted
.length
) return;
3331 static if (type
== "all") {
3332 //pragma(inline, true);
3334 } else static if (type
== "vars") {
3336 while (idx
< cmdlistSorted
.length
&& (cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]]) is null) ++idx
;
3337 } else static if (type
== "funcs") {
3339 while (idx
< cmdlistSorted
.length
&& (cast(ConFuncBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]]) is null) ++idx
;
3340 } else static if (type
== "aliases") {
3342 while (idx
< cmdlistSorted
.length
&& (cast(ConAlias
)cmdlist
[cmdlistSorted
.ptr
[idx
]]) is null) ++idx
;
3343 } else static if (type
== "archive") {
3345 while (idx
< cmdlistSorted
.length
) {
3346 if (auto v
= cast(ConVarBase
)cmdlist
[cmdlistSorted
.ptr
[idx
]]) {
3347 if (v
.attrArchive
) break;
3352 static assert(0, "wtf?!");
3356 return Range
!type(0);
3360 // ////////////////////////////////////////////////////////////////////////// //
3363 /// get console variable value (thread-safe)
3364 public T
conGetVar(T
=ConString
) (ConString s
) {
3366 scope(exit
) consoleUnlock();
3367 if (auto cc
= s
in cmdlist
) {
3368 if (auto cv
= cast(ConVarBase
)(*cc
)) return cv
.value
!T
;
3374 public T
conGetVar(T
=ConString
) (ConVarBase v
) {
3376 scope(exit
) consoleUnlock();
3377 if (v
!is null) return v
.value
!T
;
3382 /// set console variable value (thread-safe)
3383 public void conSetVar(T
) (ConString s
, T val
) {
3385 scope(exit
) consoleUnlock();
3386 if (auto cc
= s
in cmdlist
) {
3387 if (auto cv
= cast(ConVarBase
)(*cc
)) cv
.value
= val
;
3392 public void conSetVar(T
) (ConVarBase v
, T val
) {
3394 scope(exit
) consoleUnlock();
3395 if (v
!is null) v
.value
= val
;
3399 /// "seal" console variable (i.e. make it read-only) (thread-safe)
3400 public void conSealVar (ConString s
) {
3402 scope(exit
) consoleUnlock();
3403 if (auto cc
= s
in cmdlist
) {
3404 if (auto cv
= cast(ConVarBase
)(*cc
)) cv
.attrReadOnly
= true;
3409 public void conSealVar (ConVarBase v
) {
3411 scope(exit
) consoleUnlock();
3412 if (v
!is null) v
.attrReadOnly
= true;
3416 /// "seal" console variable (i.e. make it r/w) (thread-safe)
3417 public void conUnsealVar (ConString s
) {
3419 scope(exit
) consoleUnlock();
3420 if (auto cc
= s
in cmdlist
) {
3421 if (auto cv
= cast(ConVarBase
)(*cc
)) cv
.attrReadOnly
= false;
3426 public void conUnsealVar (ConVarBase v
) {
3428 scope(exit
) consoleUnlock();
3429 if (v
!is null) v
.attrReadOnly
= false;
3433 // ////////////////////////////////////////////////////////////////////////// //
3436 shared bool ccWaitPrepends
= true;
3437 public @property bool conWaitPrepends () nothrow @trusted @nogc { pragma(inline
, true); import core
.atomic
: atomicLoad
; return atomicLoad(ccWaitPrepends
); } /// shoud "wait" prepend rest of the alias?
3438 public @property void conWaitPrepends (bool v
) nothrow @trusted @nogc { pragma(inline
, true); import core
.atomic
: atomicStore
; atomicStore(ccWaitPrepends
, v
); } /// ditto
3440 __gshared
char* ccWaitPrependStr
= null;
3441 __gshared
uint ccWaitPrependStrSize
, ccWaitPrependStrLen
;
3443 ConString
prependStr () { return ccWaitPrependStr
[0..ccWaitPrependStrLen
]; }
3445 void clearPrependStr () { ccWaitPrependStrLen
= 0; return; }
3447 void setPrependStr (ConString s
) {
3448 if (s
.length
== 0) { ccWaitPrependStrLen
= 0; return; }
3449 if (s
.length
>= int.max
/8) assert(0, "console command too long"); //FIXME
3450 if (s
.length
> ccWaitPrependStrSize
) {
3451 import core
.stdc
.stdlib
: realloc
;
3452 ccWaitPrependStr
= cast(char*)realloc(ccWaitPrependStr
, s
.length
);
3453 if (ccWaitPrependStr
is null) assert(0, "out of memory in cmdcon"); //FIXME
3454 ccWaitPrependStrSize
= cast(uint)s
.length
;
3456 assert(s
.length
<= ccWaitPrependStrSize
);
3457 ccWaitPrependStrLen
= cast(uint)s
.length
;
3458 ccWaitPrependStr
[0..s
.length
] = s
[];
3462 /// execute console command (thread-safe)
3463 /// return `true` if `wait` pseudocommant was hit
3464 public bool conExecute (ConString s
) {
3465 bool waitHit
= false;
3466 auto ss
= s
; // anchor it
3468 scope(exit
) consoleUnlock();
3470 enum MaxAliasExpand
= 256*1024; // 256KB
3472 void conExecuteInternal (ConString s
, int aliassize
, int aliaslevel
) {
3473 ConString anchor
= s
, curs
= s
;
3475 if (aliaslevel
> 0) {
3476 s
= conGetCommandStr(curs
);
3477 if (s
is null) return;
3481 auto w
= ConCommand
.getWord(s
);
3482 if (w
is null) return;
3483 // "wait" pseudocommand
3486 // insert the rest of the code into command queue (at the top),
3487 // so it will be processed on the next `conProcessQueue()` call
3488 while (curs
.length
) {
3489 while (curs
.length
&& curs
.ptr
[0] <= ' ') curs
= curs
[1..$];
3490 if (curs
.length
== 0 || curs
.ptr
[0] != '#') break;
3492 while (curs
.length
&& curs
.ptr
[0] != '\n') curs
= curs
[1..$];
3495 // has something to insert
3496 if (conWaitPrepends
) {
3497 //ccWaitPrependStr = curs;
3498 //concmdPrepend(curs);
3499 setPrependStr(curs
);
3501 concmdAdd
!true(curs
); // ensure new command
3506 version(iv_cmdcon_debug_wait
) conwriteln("CMD: <", w
, ">; args=<", s
, ">; rest=<", curs
, ">");
3507 if (auto cobj
= w
in cmdlist
) {
3508 if (auto cmd
= cast(ConCommand
)(*cobj
)) {
3510 while (s
.length
&& s
.ptr
[0] <= 32) s
= s
[1..$];
3511 //conwriteln("'", s, "'");
3513 scope(exit
) consoleLock();
3515 } else if (auto ali
= cast(ConAlias
)(*cobj
)) {
3517 //TODO: alias arguments
3518 auto atext
= ali
.get
;
3519 //conwriteln("ALIAS: '", w, "': '", atext, "'");
3521 if (aliassize
+atext
.length
> MaxAliasExpand
) throw ConAlias
.exAliasTooDeep
;
3522 if (aliaslevel
>= 128) throw ConAlias
.exAliasTooDeep
;
3523 conExecuteInternal(atext
, aliassize
+cast(int)atext
.length
, aliaslevel
+1);
3526 conwrite("command ");
3527 ConCommand
.writeQuotedString(w
);
3528 conwrite(" is of unknown type (internal error)");
3532 conwrite("command ");
3533 ConCommand
.writeQuotedString(w
);
3534 conwrite(" not found");
3542 conExecuteInternal(s
, 0, 0);
3543 } catch (Exception
) {
3544 conwriteln("error executing console command:\n ", s
);
3550 // ////////////////////////////////////////////////////////////////////////// //
3552 version(contest_func
) unittest {
3553 static void xfunc (int v
, int x
=42) { conwriteln("xfunc: v=", v
, "; x=", x
); }
3555 conRegFunc
!xfunc("", "function with two int args (last has default value '42')");
3556 conExecute("xfunc ?");
3557 conExecute("xfunc 666");
3558 conExecute("xfunc");
3560 conRegFunc
!({conwriteln("!!!");})("bang", "dummy function");
3563 conRegFunc
!((ConFuncVA va
) {
3566 auto w
= ConCommand
.getWord(va
.cmdline
);
3567 if (w
is null) break;
3568 conwriteln("#", idx
, ": [", w
, "]");
3571 })("doo", "another dummy function");
3572 conExecute("doo 1 2 ' 34 '");
3576 // ////////////////////////////////////////////////////////////////////////// //
3577 /** get console commad from array of text.
3579 * console commands are delimited with newlines, but can include various quoted chars and such.
3580 * this function will take care of that, and return something suitable for passing to `conExecute`.
3582 * it will return `null` if there is no command (i.e. end-of-text reached).
3584 public ConString
conGetCommandStr (ref ConString s
) {
3586 while (s
.length
> 0 && s
[0] <= 32) s
= s
[1..$];
3587 if (s
.length
== 0) return null;
3588 if (s
.ptr
[0] != ';') break;
3594 void skipString () {
3595 char qch
= s
.ptr
[pos
++];
3596 while (pos
< s
.length
) {
3597 if (s
.ptr
[pos
] == qch
) { ++pos
; break; }
3598 if (s
.ptr
[pos
++] == '\\') {
3599 if (pos
< s
.length
) {
3600 if (s
.ptr
[pos
] == 'x' || s
.ptr
[pos
] == 'X') pos
+= 2; else ++pos
;
3607 while (pos
< s
.length
) {
3608 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
3610 } else if (s
.ptr
[pos
++] == '\n') {
3616 if (s
.ptr
[0] == '#') {
3618 if (pos
>= s
.length
) { s
= s
[$..$]; return null; }
3623 while (pos
< s
.length
) {
3624 if (s
.ptr
[pos
] == '"' || s
.ptr
[pos
] == '\'') {
3626 } else if (s
.ptr
[pos
] == ';' || s
.ptr
[pos
] == '#' || s
.ptr
[pos
] == '\n') {
3627 auto res
= s
[0..pos
];
3628 if (s
.ptr
[pos
] == '#') s
= s
[pos
..$]; else s
= s
[pos
+1..$];
3640 version(contest_cpx
) unittest {
3641 ConString s
= "boo; woo \";\" 42#cmt\ntest\nfoo";
3643 auto c
= conGetCommandStr(s
);
3644 conwriteln("[", c
, "] : [", s
, "]");
3647 auto c
= conGetCommandStr(s
);
3648 conwriteln("[", c
, "] : [", s
, "]");
3651 auto c
= conGetCommandStr(s
);
3652 conwriteln("[", c
, "] : [", s
, "]");
3655 auto c
= conGetCommandStr(s
);
3656 conwriteln("[", c
, "] : [", s
, "]");
3661 // ////////////////////////////////////////////////////////////////////////// //
3662 // console always has "echo" command, no need to register it.
3663 public class ConCommandEcho
: ConCommand
{
3664 this () { super("echo", "write string to console"); }
3666 override void exec (ConString cmdline
) {
3667 if (checkHelp(cmdline
)) { showHelp
; return; }
3668 if (!hasArgs(cmdline
)) { conwriteln
; return; }
3669 bool needSpace
= false;
3671 auto w
= getWord(cmdline
);
3672 if (w
is null) break;
3673 if (needSpace
) conwrite(" "); else needSpace
= true;
3676 while (pos
< w
.length
&& w
.ptr
[pos
] != '$') ++pos
;
3677 if (w
.length
-pos
> 1 && w
.ptr
[pos
+1] == '$') {
3678 conwrite(w
[0..pos
+1]);
3680 } else if (w
.length
-pos
<= 1) {
3686 if (pos
> 0) conwrite(w
[0..pos
]);
3688 if (w
.ptr
[pos
] == '{') {
3691 while (pos
< w
.length
&& w
.ptr
[pos
] != '}') ++pos
;
3693 if (pos
< w
.length
) ++pos
;
3698 while (pos
< w
.length
) {
3699 char ch
= w
.ptr
[pos
];
3700 if (ch
== '_' ||
(ch
>= '0' && ch
<= '9') ||
(ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z')) {
3710 if (auto cc
= vname
in cmdlist
) {
3711 if (auto cv
= cast(ConVarBase
)(*cc
)) {
3733 // ////////////////////////////////////////////////////////////////////////// //
3734 // console always has "gc_collect" command, no need to register it.
3735 public class ConCommandGCCollect
: ConCommand
{
3736 this () { super("gc_collect", "run garbage collector, and minimise working memory set."); }
3738 override void exec (ConString cmdline
) {
3739 import core
.memory
: GC
;
3740 if (checkHelp(cmdline
)) { showHelp
; return; }
3741 bool doGC
= true, doMin
= true;
3742 if (hasArgs(cmdline
)) {
3745 auto w
= getWord(cmdline
);
3746 if (w
is null) break;
3747 if (w
.strEquCI("help")) {
3748 conwriteln("use \"nogc\" to minimise only, or \"gc\" to skip minimise.");
3751 if (w
.strEquCI("nogc")) doGC
= false;
3752 else if (w
.strEquCI("gc")) { doGC
= true; doMin
= false; }
3753 else if (w
.strEquCI("minimise") || w
.strEquCI("min")) { doGC
= false; doMin
= true; }
3757 conwriteln("starting GC collection...");
3761 conwriteln("minimising working memory set...");
3764 conwriteln("GC collection complete.");
3769 // ////////////////////////////////////////////////////////////////////////// //
3770 // console always has "alias" command, no need to register it.
3771 public class ConCommandAlias
: ConCommand
{
3772 this () { super("alias", "set or remove alias"); }
3774 override void exec (ConString cmdline
) {
3775 if (checkHelp(cmdline
)) { showHelp
; return; }
3776 if (!hasArgs(cmdline
)) { conwriteln("alias what?"); return; }
3778 auto name
= getWord(cmdline
);
3779 if (name
is null || name
.length
== 0) { conwriteln("alias what?"); return; }
3780 if (name
.length
> 128) { conwriteln("alias name too long: ", name
); return; }
3782 char[129] nbuf
= void;
3783 nbuf
[0..name
.length
] = name
;
3784 const(char)[] xname
= nbuf
[0..name
.length
];
3787 auto v
= getWord(cmdline
);
3788 while (v
.length
&& v
[0] <= ' ') v
= v
[1..$];
3789 while (v
.length
&& v
[$-1] <= ' ') v
= v
[0..$-1];
3791 if (hasArgs(cmdline
)) { conwriteln("too many arguments to `alias` command"); return; }
3793 if (auto cp
= xname
in cmdlist
) {
3795 auto ali
= cast(ConAlias
)(*cp
);
3796 if (ali
is null) { conwriteln("can't change existing command/var to alias: ", xname
); return; }
3797 if (v
.length
== 0) {
3799 cmdlist
.remove(cast(string
)xname
); // it is safe to cast here
3800 foreach (immutable idx
, string n
; cmdlistSorted
) {
3802 foreach (immutable c
; idx
+1..cmdlistSorted
.length
) cmdlistSorted
[c
-1] = cmdlistSorted
[c
];
3813 auto ali
= new ConAlias(v
);
3814 //conwriteln(xname, ": <", ali.get, ">");
3815 string sname
= xname
.idup
;
3817 cmdlist
[sname
] = ali
;
3823 // ////////////////////////////////////////////////////////////////////////// //
3824 /// create "user console variable"
3825 ConVarBase
conRegUserVar(T
) (string aname
, string help
, const(ConVarAttr
)[] attrs
...) {
3826 if (aname
.length
== 0) return null;
3828 static if (is(T
: long) ||
is(T
: double)) {
3830 v
= new ConVar
!T(var
, aname
, help
);
3831 } else static if (is(T
: const(char)[])) {
3834 v
= new ConVar
!T(&var
[0], aname
, help
);
3836 static assert(0, "can't create uservar of type '"~T
.stringof
~"'");
3839 v
.mAttrs |
= ConVarAttr
.User
;
3846 /// create bounded "user console variable"
3847 ConVarBase
conRegUserVar(T
) (T amin
, T amax
, string aname
, string help
, const(ConVarAttr
)[] attrs
...) if (is(T
: long) ||
is(T
: double)) {
3848 if (aname
.length
== 0) return null;
3850 static if (is(T
: long) ||
is(T
: double)) {
3852 v
= new ConVar
!T(var
, amin
, amax
, aname
, help
);
3853 } else static if (is(T
: const(char)[])) {
3856 v
= new ConVar
!T(&var
[0], aname
, help
);
3858 static assert(0, "can't create uservar of type '"~T
.stringof
~"'");
3861 v
.mAttrs |
= ConVarAttr
.User
;
3868 // ////////////////////////////////////////////////////////////////////////// //
3869 // console always has "uvar_create" command, no need to register it.
3870 public class ConCommandUserConVar
: ConCommand
{
3871 this () { super("uvar_create", "create user convar: userconvar \"<type> <name>\"; type is <int|str|bool|float|double>"); }
3873 override void exec (ConString cmdline
) {
3874 if (checkHelp(cmdline
)) { showHelp
; return; }
3875 if (!hasArgs(cmdline
)) return;
3876 auto type
= getWord(cmdline
);
3877 auto name
= getWord(cmdline
);
3878 if (type
.length
== 0 || name
.length
== 0) return;
3879 if (name
in cmdlist
) { conwriteln("console variable '", name
, "' already exists"); return; }
3881 string aname
= name
.idup
;
3885 v
= new ConVar
!int(var
, aname
, null);
3888 //string* var = new string; // alas
3891 v
= new ConVar
!string(&var
[0], aname
, null);
3894 bool* var
= new bool;
3895 v
= new ConVar
!bool(var
, aname
, null);
3898 float* var
= new float;
3899 v
= new ConVar
!float(var
, aname
, null);
3902 double* var
= new double;
3903 v
= new ConVar
!double(var
, aname
, null);
3906 conwriteln("can't create console variable '", name
, "' of unknown type '", type
, "'");
3909 v
.setAttrs
!true(ConVarAttr
.User
);
3916 // ////////////////////////////////////////////////////////////////////////// //
3917 // console always has "uvar_remove" command, no need to register it.
3918 public class ConCommandKillUserConVar
: ConCommand
{
3919 this () { super("uvar_remove", "remove user convar: killuserconvar \"<name>\""); }
3921 override void exec (ConString cmdline
) {
3922 if (checkHelp(cmdline
)) { showHelp
; return; }
3923 if (!hasArgs(cmdline
)) return;
3924 auto name
= getWord(cmdline
);
3925 if (name
.length
== 0) return;
3926 if (auto vp
= name
in cmdlist
) {
3927 if (auto var
= cast(ConVarBase
)(*vp
)) {
3928 if (!var
.attrUser
) {
3929 conwriteln("console command '", name
, "' is not a uservar");
3933 cmdlist
.remove(cast(string
)name
); // it is safe to cast here
3934 foreach (immutable idx
, string n
; cmdlistSorted
) {
3936 foreach (immutable c
; idx
+1..cmdlistSorted
.length
) cmdlistSorted
[c
-1] = cmdlistSorted
[c
];
3941 conwriteln("console command '", name
, "' is not a var");
3945 conwriteln("console variable '", name
, "' doesn't exist");
3952 shared static this () {
3954 cmdlist
["echo"] = new ConCommandEcho();
3955 addName("gc_collect");
3956 cmdlist
["gc_collect"] = new ConCommandGCCollect();
3958 cmdlist
["alias"] = new ConCommandAlias();
3959 addName("uvar_create");
3960 cmdlist
["uvar_create"] = new ConCommandUserConVar();
3961 addName("uvar_remove");
3962 cmdlist
["uvar_remove"] = new ConCommandKillUserConVar();
3966 /** replace "$var" in string.
3968 * this function will replace "$var" in string with console var values.
3971 * dest = destination buffer
3974 public char[] conFormatStr (char[] dest
, ConString s
) {
3977 void put (ConString ss
) {
3978 if (ss
.length
== 0) return;
3979 auto len
= ss
.length
;
3980 if (dest
.length
-dpos
< len
) len
= dest
.length
-dpos
;
3982 dest
[dpos
..dpos
+len
] = ss
[];
3989 while (pos
< s
.length
&& s
.ptr
[pos
] != '$') ++pos
;
3990 if (s
.length
-pos
> 1 && s
.ptr
[pos
+1] == '$') {
3993 } else if (s
.length
-pos
<= 1) {
3999 if (pos
> 0) put(s
[0..pos
]);
4001 if (s
.ptr
[pos
] == '{') {
4004 while (pos
< s
.length
&& s
.ptr
[pos
] != '}') ++pos
;
4006 if (pos
< s
.length
) ++pos
;
4011 while (pos
< s
.length
) {
4012 char ch
= s
.ptr
[pos
];
4013 if (ch
== '_' ||
(ch
>= '0' && ch
<= '9') ||
(ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z')) {
4023 if (auto cc
= vname
in cmdlist
) {
4024 if (auto cv
= cast(ConVarBase
)(*cc
)) {
4040 return dest
[0..dpos
];
4045 version(contest_echo
) unittest {
4046 __gshared
int vi
= 42;
4047 __gshared string vs
= "str";
4048 __gshared
bool vb
= true;
4049 conRegVar
!vi("vi", "int var");
4050 conRegVar
!vs("vs", "string var");
4051 conRegVar
!vb("vb", "bool var");
4052 conRegVar
!vb("r_interpolation", "bool var");
4053 conwriteln("=================");
4054 conExecute("r_interpolation");
4055 conExecute("echo ?");
4056 conExecute("echo vs=$vs, vi=${vi}, vb=${vb}!");
4059 auto s
= buf
.conFormatStr("vs=$vs, vi=${vi}, vb=${vb}!");
4060 conwriteln("[", s
, "]");
4061 foreach (/*auto*/ kv
; cmdlist
.byKeyValue
) conwriteln(" ", kv
.key
);
4062 assert("r_interpolation" in cmdlist
);
4063 s
= buf
.conFormatStr("Interpolation: $r_interpolation");
4064 conwriteln("[", s
, "]");
4066 conwriteln("vi=", conGetVar
!int("vi"));
4067 conwriteln("vi=", conGetVar("vi"));
4068 conwriteln("vs=", conGetVar("vi"));
4069 conwriteln("vb=", conGetVar
!int("vb"));
4070 conwriteln("vb=", conGetVar
!bool("vb"));
4071 conwriteln("vb=", conGetVar("vb"));
4076 version(contest_cmdlist
) unittest {
4078 auto cl
= conByCommand
;
4079 conwriteln("=== all ===");
4081 if (cl
.frontIsVar
) conwrite("VAR ");
4082 else if (cl
.frontIsFunc
) conwrite("FUNC ");
4083 else conwrite("UNK ");
4084 conwriteln("[", cl
.front
, "]");
4089 conwriteln("=== funcs ===");
4090 foreach (/*auto*/ clx
; conByCommand
!"funcs") conwriteln(" [", clx
, "]");
4092 conwriteln("=== vars ===");
4093 foreach (/*auto*/ clx
; conByCommand
!"vars") conwriteln(" [", clx
, "]");
4097 shared static this () {
4100 foreach (string name
; cmdlistSorted
[]) {
4101 if (auto ccp
= name
in cmdlist
) {
4102 if (name
.length
> 64) {
4103 if (maxlen
< 64) maxlen
= 64;
4105 if (name
.length
> maxlen
) maxlen
= cast(uint)name
.length
;
4109 foreach (string name
; cmdlistSorted
[]) {
4110 if (auto ccp
= name
in cmdlist
) {
4112 foreach (immutable _
; name
.length
..maxlen
) conwrite(" ");
4113 if (auto cmd
= cast(ConCommand
)(*ccp
)) {
4114 conwriteln(" -- ", cmd
.help
);
4116 conwriteln(" -- alias");
4120 })("cmdlist", "list all known commands and variables");
4124 // ////////////////////////////////////////////////////////////////////////// //
4125 // simple input buffer for console
4127 __gshared
char[4096] concli
= 0;
4128 __gshared
uint conclilen
= 0;
4129 __gshared
int concurx
= 0;
4130 public __gshared
void delegate () nothrow @trusted conInputChangedCB
; /// can be called in any thread
4132 private __gshared
uint inchangeCount
= 1;
4133 public @property uint conInputLastChange () nothrow @trusted @nogc { pragma(inline
, true); import iv
.atomic
; return atomicLoad(inchangeCount
); } /// changed when something was put to console input buffer (thread-safe)
4134 public void conInputIncLastChange () nothrow @trusted { pragma(inline
, true); import iv
.atomic
; atomicFetchAdd(inchangeCount
, 1); if (conInputChangedCB
!is null) conInputChangedCB(); } /// increment console input buffer change flag (thread-safe)
4136 public @property ConString
conInputBuffer() () @trusted @nogc { pragma(inline
, true); return concli
[0..conclilen
]; } /// returns console input buffer (not thread-safe)
4137 public @property int conInputBufferCurX() () @trusted @nogc { pragma(inline
, true); return concurx
; } /// returns cursor position in input buffer: [0..conclilen] (not thread-safe)
4140 /** clear console input buffer. (not thread-safe)
4142 * call this function with `addToHistory:true` to add current input buffer to history.
4143 * it is safe to call this for empty input buffer, history won't get empty line.
4146 * addToHistory = true if current buffer should be "added to history", so current history index should be reset and history will be updated
4148 public void conInputBufferClear() (bool addToHistory
=false) @trusted {
4149 if (conclilen
> 0) {
4150 if (addToHistory
) conHistoryAdd(conInputBuffer
);
4153 conInputIncLastChange();
4155 if (addToHistory
) conhisidx
= -1;
4159 private struct ConHistItem
{
4160 char* dbuf
; // without trailing 0
4161 char[256] sbuf
= 0; // static buffer
4162 uint len
; // of dbuf or sbuf, without trailing 0
4163 uint alloted
; // !0: `dbuf` is used
4164 nothrow @trusted @nogc:
4165 void releaseMemory () { import core
.stdc
.stdlib
: free
; if (dbuf
!is null) free(dbuf
); dbuf
= null; alloted
= len
= 0; }
4166 @property const(char)[] str () { pragma(inline
, true); return (alloted ? dbuf
: sbuf
.ptr
)[0..len
]; }
4167 @property void str (const(char)[] s
) {
4168 import core
.stdc
.stdlib
: malloc
, realloc
;
4169 if (s
.length
> 65536) s
= s
[0..65536]; // just in case
4170 if (s
.length
== 0) { len
= 0; return; }
4171 // try to put in allocated space
4173 if (s
.length
> alloted
) {
4174 auto np
= cast(char*)realloc(dbuf
, s
.length
);
4176 // can't allocate memory
4177 dbuf
[0..alloted
] = s
[0..alloted
];
4181 alloted
= cast(uint)s
.length
;
4184 dbuf
[0..s
.length
] = s
[];
4185 len
= cast(uint)s
.length
;
4188 // fit in static buf?
4189 if (s
.length
<= sbuf
.length
) {
4190 sbuf
.ptr
[0..s
.length
] = s
[];
4191 len
= cast(uint)s
.length
;
4194 // allocate dynamic buffer
4195 dbuf
= cast(char*)malloc(s
.length
);
4197 // alas; trim and use static one
4198 sbuf
[] = s
[0..sbuf
.length
];
4199 len
= cast(uint)sbuf
.length
;
4201 // ok, use dynamic buffer
4202 alloted
= len
= cast(uint)s
.length
;
4209 //TODO: make circular buffer
4210 private enum ConHistMax
= 8192;
4211 __gshared ConHistItem
* concmdhistory
= null;
4212 __gshared
int conhisidx
= -1;
4213 __gshared
uint conhismax
= 128;
4214 __gshared
uint conhisused
= 0;
4215 __gshared
uint conhisalloted
= 0;
4218 shared static this () {
4219 conRegVar
!conhismax(1, ConHistMax
, "r_conhistmax", "maximum commands in console input history");
4223 // free unused slots if `conhismax` was changed
4224 private void conHistShrinkBuf () {
4225 import core
.stdc
.stdlib
: realloc
;
4226 import core
.stdc
.string
: memmove
, memset
;
4227 if (conhisalloted
<= conhismax
) return;
4228 auto tokill
= conhisalloted
-conhismax
;
4229 debug(concmd_history
) conwriteln("removing ", tokill
, " items out of ", conhisalloted
);
4230 // discard old items
4231 if (conhisused
> conhismax
) {
4232 auto todis
= conhisused
-conhismax
;
4233 debug(concmd_history
) conwriteln("discarding ", todis
, " items out of ", conhisused
);
4235 foreach (ref ConHistItem it
; concmdhistory
[0..todis
]) it
.releaseMemory();
4236 // move array elements
4237 memmove(concmdhistory
, concmdhistory
+todis
, ConHistItem
.sizeof
*conhismax
);
4238 // clear what is left
4239 memset(concmdhistory
+conhismax
, 0, ConHistItem
.sizeof
*todis
);
4240 conhisused
= conhismax
;
4243 auto np
= cast(ConHistItem
*)realloc(concmdhistory
, ConHistItem
.sizeof
*conhismax
);
4244 if (np
!is null) concmdhistory
= np
;
4245 conhisalloted
= conhismax
;
4249 // allocate space for new command, return it's index or -1 on error
4250 private int conHistAllot () {
4251 import core
.stdc
.stdlib
: realloc
;
4252 import core
.stdc
.string
: memmove
, memset
;
4253 conHistShrinkBuf(); // shrink buffer, if necessary
4254 if (conhisused
>= conhisalloted
&& conhisused
< conhismax
) {
4256 uint newsz
= conhisalloted
+64;
4257 if (newsz
> conhismax
) newsz
= conhismax
;
4258 debug(concmd_history
) conwriteln("adding ", newsz
-conhisalloted
, " items (now: ", conhisalloted
, ")");
4259 auto np
= cast(ConHistItem
*)realloc(concmdhistory
, ConHistItem
.sizeof
*newsz
);
4264 memset(concmdhistory
+conhisalloted
, 0, ConHistItem
.sizeof
*(newsz
-conhisalloted
));
4265 conhisalloted
= newsz
; // fix it! ;-)
4266 return conhisused
++;
4268 // alas, have to move
4270 if (conhisalloted
== 0) return -1; // no memory
4271 if (conhisalloted
== 1) { conhisused
= 1; return 0; } // always
4272 assert(conhisused
<= conhisalloted
);
4273 if (conhisused
== conhisalloted
) {
4274 ConHistItem tmp
= concmdhistory
[0];
4277 memmove(concmdhistory
, concmdhistory
+1, ConHistItem
.sizeof
*conhisused
);
4278 //memset(concmdhistory+conhisused, 0, ConHistItem.sizeof);
4279 memmove(concmdhistory
+conhisused
, &tmp
, ConHistItem
.sizeof
);
4281 return conhisused
++;
4285 /// returns input buffer history item (0 is oldest) or null if there are no more items (not thread-safe)
4286 public ConString
conHistoryAt (int idx
) {
4287 if (idx
< 0 || idx
>= conhisused
) return null;
4288 return concmdhistory
[conhisused
-idx
-1].str;
4292 /// find command in input history, return index or -1 (not thread-safe)
4293 public int conHistoryFind (ConString cmd
) {
4294 while (cmd
.length
&& cmd
[0] <= 32) cmd
= cmd
[1..$];
4295 while (cmd
.length
&& cmd
[$-1] <= 32) cmd
= cmd
[0..$-1];
4296 if (cmd
.length
== 0) return -1;
4297 foreach (int idx
; 0..conhisused
) {
4298 auto c
= concmdhistory
[idx
].str;
4299 while (c
.length
> 0 && c
[0] <= 32) c
= c
[1..$];
4300 while (c
.length
> 0 && c
[$-1] <= 32) c
= c
[0..$-1];
4301 if (c
== cmd
) return conhisused
-idx
-1;
4307 /// add command to history. will take care about duplicate commands. (not thread-safe)
4308 public void conHistoryAdd (ConString cmd
) {
4309 import core
.stdc
.string
: memmove
, memset
;
4311 while (cmd
.length
&& cmd
[0] <= 32) cmd
= cmd
[1..$];
4312 while (cmd
.length
&& cmd
[$-1] <= 32) cmd
= cmd
[0..$-1];
4313 if (cmd
.length
== 0) return;
4314 auto idx
= conHistoryFind(cmd
);
4316 debug(concmd_history
) conwriteln("command found! idx=", idx
, "; real idx=", conhisused
-idx
-1, "; used=", conhisused
);
4317 idx
= conhisused
-idx
-1; // fix index
4318 // move command to bottom
4319 if (idx
== conhisused
-1) return; // nothing to do
4321 ConHistItem tmp
= concmdhistory
[idx
];
4323 memmove(concmdhistory
+idx
, concmdhistory
+idx
+1, ConHistItem
.sizeof
*(conhisused
-idx
-1));
4324 //memset(concmdhistory+conhisused, 0, ConHistItem.sizeof);
4325 memmove(concmdhistory
+conhisused
-1, &tmp
, ConHistItem
.sizeof
);
4328 idx
= conHistAllot();
4329 if (idx
< 0) return; // alas
4330 concmdhistory
[idx
].str = orgcmd
;
4335 /// special characters for `conAddInputChar()`
4336 public enum ConInputChar
: char {
4343 PageUp
= '\x07', ///
4344 Backspace
= '\x08', ///
4347 PageDown
= '\x0b', ///
4348 Delete
= '\x0c', ///
4350 Insert
= '\x0e', ///
4353 LineUp
= '\x1a', ///
4354 LineDown
= '\x1b', ///
4359 /// process console input char (won't execute commands, but will do autocompletion and history) (not thread-safe)
4360 public void conAddInputChar (char ch
) {
4361 __gshared
int prevWasEmptyAndTab
= 0;
4363 bool insChars (const(char)[] s
...) {
4364 if (s
.length
== 0) return false;
4365 if (concli
.length
-conclilen
< s
.length
) return false;
4366 foreach (char ch
; s
) {
4367 if (concurx
== conclilen
) {
4368 concli
.ptr
[conclilen
++] = ch
;
4371 import core
.stdc
.string
: memmove
;
4372 memmove(concli
.ptr
+concurx
+1, concli
.ptr
+concurx
, conclilen
-concurx
);
4373 concli
.ptr
[concurx
++] = ch
;
4381 if (ch
== ConInputChar
.Tab
) {
4383 if (++prevWasEmptyAndTab
< 2) return;
4385 prevWasEmptyAndTab
= 0;
4388 // if there are space(s) before cursor position, this is argument completion
4389 bool doArgAC
= false;
4392 while (p
> 0 && concli
.ptr
[p
-1] > ' ') --p
;
4396 prevWasEmptyAndTab
= 0;
4397 // yeah, arguments; first, get command name
4399 while (stp
< concurx
&& concli
.ptr
[stp
] <= ' ') ++stp
;
4400 if (stp
>= concurx
) return; // alas
4402 while (ste
< concurx
&& concli
.ptr
[ste
] > ' ') ++ste
;
4403 if (auto cp
= concli
[stp
..ste
] in cmdlist
) {
4404 if (auto cmd
= cast(ConCommand
)(*cp
)) {
4405 if (cmd
.argcomplete
!is null) try { cmd
.argcomplete(cmd
); } catch (Exception
) {} // sorry
4410 string minPfx
= null;
4411 // find longest command
4412 foreach (/*auto*/ name
; conByCommand
) {
4413 if (name
.length
>= concurx
&& name
.length
> minPfx
.length
&& name
[0..concurx
] == concli
[0..concurx
]) minPfx
= name
;
4415 string longestCmd
= minPfx
;
4416 //conwriteln("longest command: [", minPfx, "]");
4417 // find longest prefix
4418 foreach (/*auto*/ name
; conByCommand
) {
4419 if (name
.length
< concurx
) continue;
4420 if (name
[0..concurx
] != concli
[0..concurx
]) continue;
4422 while (pos
< name
.length
&& pos
< minPfx
.length
&& minPfx
.ptr
[pos
] == name
.ptr
[pos
]) ++pos
;
4423 if (pos
< minPfx
.length
) minPfx
= minPfx
[0..pos
];
4425 if (minPfx
.length
> concli
.length
) minPfx
= minPfx
[0..concli
.length
];
4426 //conwriteln("longest prefix : [", minPfx, "]");
4427 if (minPfx
.length
>= concurx
) {
4428 // wow! has something to add
4429 bool doRet
= (minPfx
.length
> concurx
);
4430 if (insChars(minPfx
[concurx
..$])) {
4431 // insert space after complete command
4432 if (longestCmd
.length
== minPfx
.length
&& conHasCommand(minPfx
)) {
4433 if (concurx
>= conclilen || concli
.ptr
[concurx
] > ' ') {
4434 doRet
= insChars(' ');
4440 conInputIncLastChange();
4445 // nope, print all available commands
4446 bool needDelimiter
= true;
4447 foreach (/*auto*/ name
; conByCommand
) {
4448 if (name
.length
== 0) continue;
4450 if (name
.length
< concurx
) continue;
4451 if (name
[0..concurx
] != concli
[0..concurx
]) continue;
4453 // skip "unusal" commands"
4455 if (!((ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z') || ch
== '_')) continue;
4457 if (needDelimiter
) { conwriteln("----------------"); needDelimiter
= false; }
4462 // process other keys
4463 prevWasEmptyAndTab
= 0;
4465 if (ch
== ConInputChar
.Backspace
) {
4467 if (concurx
< conclilen
) {
4468 import core
.stdc
.string
: memmove
;
4469 memmove(concli
.ptr
+concurx
-1, concli
.ptr
+concurx
, conclilen
-concurx
);
4473 conInputIncLastChange();
4478 if (ch
== ConInputChar
.Delete
) {
4479 if (concurx
< conclilen
) {
4480 if (conclilen
> 1) {
4481 import core
.stdc
.string
: memmove
;
4482 memmove(concli
.ptr
+concurx
, concli
.ptr
+concurx
+1, conclilen
-concurx
-1);
4485 conInputIncLastChange();
4489 // ^W (delete previous word)
4490 if (ch
== ConInputChar
.CtrlW
) {
4494 while (stp
> 0 && concli
.ptr
[stp
-1] <= ' ') --stp
;
4496 while (stp
> 0 && concli
.ptr
[stp
-1] > ' ') --stp
;
4497 if (stp
< concurx
) {
4498 import core
.stdc
.string
: memmove
;
4499 // delete from stp to concurx
4500 int dlen
= concurx
-stp
;
4501 assert(concurx
<= conclilen
);
4502 if (concurx
< conclilen
) memmove(concli
.ptr
+stp
, concli
.ptr
+concurx
, conclilen
-concurx
);
4505 conInputIncLastChange();
4511 if (ch
== ConInputChar
.CtrlY
) {
4512 if (conclilen
> 0) { conclilen
= 0; concurx
= 0; conInputIncLastChange(); }
4516 if (ch
== ConInputChar
.Home
) {
4519 conInputIncLastChange();
4524 if (ch
== ConInputChar
.End
) {
4525 if (concurx
< conclilen
) {
4526 concurx
= conclilen
;
4527 conInputIncLastChange();
4532 if (ch
== ConInputChar
.Up
) {
4534 auto cmd
= conHistoryAt(conhisidx
);
4535 if (cmd
.length
== 0) {
4538 concli
[0..cmd
.length
] = cmd
[];
4539 conclilen
= cast(uint)cmd
.length
;
4540 concurx
= conclilen
;
4541 conInputIncLastChange();
4546 if (ch
== ConInputChar
.Down
) {
4548 auto cmd
= conHistoryAt(conhisidx
);
4549 if (cmd
.length
== 0 && conhisidx
< -1) {
4552 concli
[0..cmd
.length
] = cmd
[];
4553 conclilen
= cast(uint)cmd
.length
;
4554 concurx
= conclilen
;
4555 conInputIncLastChange();
4560 if (ch
== ConInputChar
.Left
) {
4563 conInputIncLastChange();
4568 if (ch
== ConInputChar
.Right
) {
4569 if (concurx
< conclilen
) {
4571 conInputIncLastChange();
4576 if (ch
< ' ' || ch
> 127) return;
4577 if (insChars(ch
)) conInputIncLastChange();
4581 // ////////////////////////////////////////////////////////////////////////// //
4584 /// add console command to execution queue (thread-safe)
4585 public void concmd (ConString cmd
) {
4587 scope(exit
) { consoleUnlock(); conInputIncLastChange(); }
4592 /// add console command to execution queue (thread-safe)
4593 /// this understands '%s' and '%q' (quoted string, but without surroinding quotes)
4594 /// it also understands '$var', '${var}', '${var:ifabsent}', '${var?ifempty}'
4595 /// var substitution in quotes will be automatically quoted
4596 /// string substitution in quotes will be automatically quoted
4597 public void concmdf(string fmt
, A
...) (A args
) { concmdfdg(fmt
, null, args
); }
4600 /// add console command to execution queue (thread-safe)
4601 /// this understands '%s' and '%q' (quoted string, but without surroinding quotes)
4602 /// it also understands '$var', '${var}', '${var:ifabsent}', '${var?ifempty}'
4603 /// var substitution in quotes will be automatically quoted
4604 /// string substitution in quotes will be automatically quoted
4605 public void concmdfdg(A
...) (ConString fmt
, scope void delegate (ConString cmd
) cmddg
, A args
) {
4607 scope(exit
) { consoleUnlock(); conInputIncLastChange(); }
4609 auto fmtanchor
= fmt
;
4611 bool ensureCmd
= true;
4614 usize stpos
= usize
.max
;
4616 void puts(bool quote
=false) (const(char)[] s
...) {
4618 if (ensureCmd
) { concmdEnsureNewCommand(); ensureCmd
= false; }
4619 if (stpos
== usize
.max
) stpos
= concmdbufpos
;
4622 foreach (immutable idx
, char ch
; s
) {
4623 if (ch
< ' ' || ch
== 127 || ch
== '#' || ch
== '"' || ch
== '\\') {
4624 import core
.stdc
.stdio
: snprintf
;
4625 auto len
= snprintf(buf
.ptr
, buf
.length
, "\\x%02x", cast(uint)ch
);
4626 concmdAdd
!false(buf
[0..len
]);
4628 concmdAdd
!false(s
[idx
..idx
+1]);
4637 void putint(TT
) (TT nn
) {
4640 static if (is(TT
== shared)) {
4643 T n
= atomicLoad(nn
);
4649 static if (is(T
== long)) {
4650 if (n
== 0x8000_0000_0000_0000uL) { puts("-9223372036854775808"); return; }
4651 } else static if (is(T
== int)) {
4652 if (n
== 0x8000_0000u) { puts("-2147483648"); return; }
4653 } else static if (is(T
== short)) {
4654 if ((n
&0xffff) == 0x8000u
) { puts("-32768"); return; }
4655 } else static if (is(T
== byte)) {
4656 if ((n
&0xff) == 0x80u
) { puts("-128"); return; }
4659 static if (__traits(isUnsigned
, T
)) {
4666 int bpos
= buf
.length
;
4668 //if (bpos == 0) assert(0, "internal printer error");
4669 buf
.ptr
[--bpos
] = cast(char)('0'+n
%10);
4673 //if (bpos == 0) assert(0, "internal printer error");
4674 buf
.ptr
[--bpos
] = '-';
4679 void putfloat(TT
) (TT nn
) {
4680 import core
.stdc
.stdlib
: malloc
, realloc
;
4682 static if (is(TT
== shared)) {
4685 T n
= atomicLoad(nn
);
4692 static usize buflen
= 0;
4696 buf
= cast(char*)malloc(buflen
);
4697 if (buf
is null) assert(0, "out of memory");
4701 import core
.stdc
.stdio
: snprintf
;
4702 auto plen
= snprintf(buf
, buflen
, "%g", cast(double)n
);
4703 if (plen
>= buflen
) {
4705 buf
= cast(char*)realloc(buf
, buflen
);
4706 if (buf
is null) assert(0, "out of memory");
4714 static bool isGoodIdChar (char ch
) pure nothrow @safe @nogc {
4715 pragma(inline
, true);
4717 (ch
>= '0' && ch
<= '9') ||
4718 (ch
>= 'A' && ch
<= 'Z') ||
4719 (ch
>= 'a' && ch
<= 'z') ||
4723 void processUntilFSp () {
4724 while (pos
< fmt
.length
) {
4726 while (epos
< fmt
.length
&& fmt
[epos
] != '%') {
4727 if (fmt
[epos
] == '\\' && fmt
.length
-epos
> 1 /*&& (fmt[epos+1] == '"' || fmt[epos+1] == '$')*/) {
4728 puts(fmt
[pos
..epos
]);
4733 if (inQ
&& fmt
[epos
] == inQ
) inQ
= 0;
4734 if (fmt
[epos
] == '"') {
4736 } else if (fmt
[epos
] == '$') {
4737 puts(fmt
[pos
..epos
]);
4739 if (fmt
.length
-epos
> 0 && fmt
[epos
+1] != '{') {
4742 while (epos
< fmt
.length
&& isGoodIdChar(fmt
[epos
])) ++epos
;
4744 if (auto cc
= fmt
[pos
+1..epos
] in cmdlist
) {
4745 if (auto cv
= cast(ConVarBase
)(*cc
)) {
4746 if (inQ
) puts
!true(cv
.strval
); else puts
!false(cv
.strval
);
4756 //FIXME: allow escaping of '}'
4757 while (epos
< fmt
.length
&& fmt
[epos
] != '}') ++epos
;
4758 if (epos
>= fmt
.length
) { epos
= pos
+1; continue; } // no '}'
4761 while (cpos
< epos
&& fmt
[cpos
] != ':' && fmt
[cpos
] != '?') ++cpos
;
4762 if (auto cc
= fmt
[pos
..cpos
] in cmdlist
) {
4763 if (auto cv
= cast(ConVarBase
)(*cc
)) {
4764 auto sv
= cv
.strval
;
4765 // process replacement value for empty strings
4766 if (cpos
< epos
&& fmt
[cpos
] == '?' && sv
.length
== 0) {
4767 if (inQ
) puts
!true(fmt
[cpos
+1..epos
]); else puts
!false(fmt
[cpos
+1..epos
]);
4769 if (inQ
) puts
!true(sv
); else puts
!false(sv
);
4775 // either no such command, or it's not a var
4776 if (cpos
< epos
&& (fmt
[cpos
] == '?' || fmt
[cpos
] == ':')) {
4777 if (inQ
) puts
!true(fmt
[cpos
+1..epos
]); else puts
!false(fmt
[cpos
+1..epos
]);
4786 puts(fmt
[pos
..epos
]);
4788 if (pos
>= fmt
.length
) break;
4790 if (fmt
.length
-pos
< 2 || fmt
[pos
+1] == '%') { puts('%'); pos
+= 2; continue; }
4795 foreach (immutable argnum
, /*auto*/ att
; A
) {
4797 if (pos
>= fmt
.length
) assert(0, "out of format specifiers for arguments");
4798 assert(fmt
[pos
] == '%');
4800 if (pos
>= fmt
.length
) assert(0, "out of format specifiers for arguments");
4801 alias at
= XUQQ
!att
;
4802 bool doQuote
= false;
4803 switch (fmt
[pos
++]) {
4805 static if (is(at
== char)) {
4806 puts
!true(args
[argnum
]);
4807 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
4808 import std
.conv
: to
;
4809 puts
!true(to
!string(args
[argnum
]));
4810 } else static if (is(at
== bool)) {
4811 puts
!false(args
[argnum
] ?
"true" : "false");
4812 } else static if (is(at
== enum)) {
4813 bool dumpNum
= true;
4814 foreach (string mname
; __traits(allMembers
, at
)) {
4815 if (args
[argnum
] == __traits(getMember
, at
, mname
)) {
4822 if (dumpNum
) putint
!long(cast(long)args
[argnum
]);
4823 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
4824 putfloat(args
[argnum
]);
4825 } else static if (is(at
: const(char)[])) {
4826 puts
!true(args
[argnum
]);
4827 } else static if (is(at
: T
*, T
)) {
4828 assert(0, "can't put pointers");
4830 import std
.conv
: to
;
4831 puts
!true(to
!string(args
[argnum
]));
4837 if (inQ
) goto case 'q';
4838 static if (is(at
== char)) {
4840 } else static if (is(at
== wchar) ||
is(at
== dchar)) {
4841 import std
.conv
: to
;
4842 puts(to
!string(args
[argnum
]));
4843 } else static if (is(at
== bool)) {
4844 puts(args
[argnum
] ?
"true" : "false");
4845 } else static if (is(at
== enum)) {
4846 bool dumpNum
= true;
4847 foreach (string mname
; __traits(allMembers
, at
)) {
4848 if (args
[argnum
] == __traits(getMember
, at
, mname
)) {
4855 if (dumpNum
) putint
!long(cast(long)args
[argnum
]);
4856 } else static if (is(at
== float) ||
is(at
== double) ||
is(at
== real)) {
4857 putfloat(args
[argnum
]);
4858 } else static if (is(at
: const(char)[])) {
4860 } else static if (is(at
: T
*, T
)) {
4861 assert(0, "can't put pointers");
4863 import std
.conv
: to
;
4864 puts(to
!string(args
[argnum
]));
4868 assert(0, "invalid format specifier");
4872 while (pos
< fmt
.length
) {
4874 if (pos
>= fmt
.length
) break;
4875 assert(0, "out of args for format specifier");
4878 //conwriteln(concmdbuf[0..concmdbufpos]);
4879 if (cmddg
!is null) {
4880 if (stpos
!= usize
.max
) {
4881 cmddg(concmdbuf
[stpos
..concmdbufpos
]);
4889 /// get console variable value; doesn't do complex conversions! (thread-safe)
4890 public T
convar(T
) (ConString s
) {
4892 scope(exit
) consoleUnlock();
4893 return conGetVar
!T(s
);
4896 /// set console variable value; doesn't do complex conversions! (thread-safe)
4897 /// WARNING! this is instant action, execution queue and r/o (and other) flags are ignored!
4898 public void convar(T
) (ConString s
, T val
) {
4900 scope(exit
) consoleUnlock();
4901 conSetVar
!T(s
, val
);
4905 // ////////////////////////////////////////////////////////////////////////// //
4906 __gshared
char[] concmdbuf
;
4907 __gshared
uint concmdbufpos
;
4908 shared static this () { concmdbuf
.unsafeArraySetLength(65536); }
4911 private void unsafeArraySetLength(T
) (ref T
[] arr
, int newlen
) /*nothrow*/ {
4912 if (newlen
< 0 || newlen
>= int.max
/2) assert(0, "invalid number of elements in array");
4913 if (arr
.length
> newlen
) {
4914 arr
.length
= newlen
;
4915 arr
.assumeSafeAppend
;
4916 } else if (arr
.length
< newlen
) {
4917 auto optr
= arr
.ptr
;
4918 arr
.length
= newlen
;
4919 if (arr
.ptr
!is optr
) {
4920 import core
.memory
: GC
;
4922 if (optr
is GC
.addrOf(optr
)) GC
.setAttr(optr
, GC
.BlkAttr
.NO_INTERIOR
);
4928 void concmdPrepend (ConString s
) {
4929 if (s
.length
== 0) return; // nothing to do
4930 if (s
.length
>= int.max
/4) assert(0, "console command too long"); //FIXME
4931 uint reslen
= cast(uint)s
.length
+1;
4932 uint newlen
= concmdbufpos
+reslen
;
4933 if (newlen
<= concmdbufpos || newlen
>= int.max
/2-1024) assert(0, "console command too long"); //FIXME
4934 if (newlen
> concmdbuf
.length
) concmdbuf
.unsafeArraySetLength(newlen
+512);
4936 if (concmdbufpos
> 0) {
4937 import core
.stdc
.string
: memmove
;
4938 memmove(concmdbuf
.ptr
+reslen
, concmdbuf
.ptr
, concmdbufpos
);
4940 // put new test and '\n'
4941 concmdbuf
.ptr
[0..s
.length
] = s
[];
4942 concmdbuf
.ptr
[s
.length
] = '\n';
4943 concmdbufpos
+= reslen
;
4947 void concmdEnsureNewCommand () {
4948 if (concmdbufpos
> 0 && concmdbuf
[concmdbufpos
-1] != '\n') {
4949 if (concmdbuf
.length
-concmdbufpos
< 1) concmdbuf
.unsafeArraySetLength(cast(int)concmdbuf
.length
+512);
4951 concmdbuf
.ptr
[concmdbufpos
++] = '\n';
4955 package(iv
) void concmdAdd(bool ensureNewCommand
=true) (ConString s
) {
4957 if (concmdbuf
.length
-concmdbufpos
< s
.length
+1) {
4958 concmdbuf
.unsafeArraySetLength(cast(int)(concmdbuf
.length
+s
.length
-(concmdbuf
.length
-concmdbufpos
)+512));
4960 static if (ensureNewCommand
) {
4961 if (concmdbufpos
> 0 && concmdbuf
[concmdbufpos
-1] != '\n') concmdbuf
.ptr
[concmdbufpos
++] = '\n';
4963 concmdbuf
[concmdbufpos
..concmdbufpos
+s
.length
] = s
[];
4964 concmdbufpos
+= s
.length
;
4969 /** does console has anything in command queue? (not thread-safe)
4971 * note that it can be anything, including comment or some blank chars.
4974 * `true` is queue is empty.
4976 public bool conQueueEmpty () {
4977 return (concmdbufpos
== 0);
4981 /** execute commands added with `concmd()`. (thread-safe)
4983 * all commands added during execution of this function will be postponed for the next call.
4984 * call this function in your main loop to process all accumulated console commands.
4989 * maxlen = maximum queue size to process (0: don't process commands added while processing ;-)
4992 * "has more commands" flag (i.e. some new commands were added to queue)
4994 public bool conProcessQueue (uint maxlen
=0) {
4995 import core
.stdc
.stdlib
: realloc
;
4996 static char* tmpcmdbuf
= null;
4997 static uint tmpcmdbufsize
= 0;
4998 scope(exit
) { consoleLock(); clearPrependStr(); conHistShrinkBuf(); consoleUnlock(); } // do it here
5001 scope(exit
) consoleUnlock();
5002 if (concmdbufpos
== 0) return false;
5004 bool once
= (maxlen
== 0);
5005 if (once
) maxlen
= uint.max
;
5010 scope(exit
) consoleUnlock();
5011 auto ebuf
= concmdbufpos
;
5012 s
= concmdbuf
[0..ebuf
];
5013 version(iv_cmdcon_debug_wait
) conwriteln("** [", s
, "]; ebuf=", ebuf
);
5014 if (tmpcmdbufsize
< s
.length
) {
5015 tmpcmdbuf
= cast(char*)realloc(tmpcmdbuf
, s
.length
+1);
5016 if (tmpcmdbuf
is null) {
5017 // here, we are dead and fucked (the exact order doesn't matter)
5018 import core
.stdc
.stdlib
: abort
;
5019 import core
.stdc
.stdio
: fprintf
, stderr
;
5020 import core
.memory
: GC
;
5022 GC
.disable(); // yeah
5023 thread_suspendAll(); // stop right here, you criminal scum!
5024 fprintf(stderr
, "\n=== FATAL: OUT OF MEMORY IN COMMAND CONSOLE!\n");
5025 abort(); // die, you bitch!
5027 tmpcmdbufsize
= cast(uint)s
.length
+1;
5029 import core
.stdc
.string
: memcpy
;
5030 if (s
.length
) { memcpy(tmpcmdbuf
, s
.ptr
, s
.length
); s
= tmpcmdbuf
[0..s
.length
]; }
5032 if (maxlen
<= ebuf
) maxlen
= 0; else maxlen
-= ebuf
;
5035 if (s
.length
== 0) break;
5039 auto cmd
= conGetCommandStr(s
);
5040 if (cmd
is null) break;
5041 if (cmd
.length
> int.max
/8) { conwriteln("command too long"); continue; }
5043 version(iv_cmdcon_debug_wait
) conwriteln("** <", cmd
, ">");
5044 if (conExecute(cmd
)) {
5045 // "wait" pseudocommand hit; prepend what is left
5046 version(iv_cmdcon_debug_wait
) conwriteln(" :: rest=<", s
, ">");
5048 scope(exit
) consoleUnlock();
5049 if (ccWaitPrependStrLen
) { concmdPrepend(prependStr
); clearPrependStr(); }
5050 if (s
.length
) { setPrependStr(s
); concmdPrepend(prependStr
); clearPrependStr(); }
5054 version(iv_cmdcon_debug_wait
) conwriteln(" ++++++++++");
5055 } catch (Exception e
) {
5056 conwriteln("***ERROR: ", e
.msg
);
5059 if (once || maxlen
== 0) break;
5063 scope(exit
) consoleUnlock();
5064 if (ccWaitPrependStrLen
) { concmdPrepend(prependStr
); clearPrependStr(); }
5065 return (concmdbufpos
== 0);
5070 // ////////////////////////////////////////////////////////////////////////// //
5071 /** process command-line arguments, put 'em to console queue, remove from array (thread-safe).
5073 * just call this function from `main (string[] args)`, with `args`.
5075 * console args looks like:
5076 * +cmd arg arg +cmd arg arg
5079 * immediate = immediately call `conProcessQueue()` with some big limit
5080 * args = those arg vector passed to `main()`
5083 * `true` is any command was added to queue.
5085 public bool conProcessArgs(bool immediate
=false) (ref string
[] args
) {
5087 //scope(exit) consoleUnlock();
5089 bool ensureCmd
= true;
5090 auto ocbpos
= concmdbufpos
;
5092 void puts (const(char)[] s
...) {
5095 concmdEnsureNewCommand();
5098 concmdAdd
!false(" "); // argument delimiter
5100 // check if we need to quote arg
5101 bool doQuote
= false;
5102 foreach (char ch
; s
) if (ch
<= ' ' || ch
== 127 || ch
== '"' || ch
== '#') { doQuote
= true; break; }
5104 concmdAdd
!false("\"");
5105 foreach (immutable idx
, char ch
; s
) {
5106 if (ch
< ' ' || ch
== 127 || ch
== '"' || ch
== '\\') {
5107 import core
.stdc
.stdio
: snprintf
;
5109 auto len
= snprintf(buf
.ptr
, buf
.length
, "\\x%02x", cast(uint)ch
);
5110 if (len
<= 0) assert(0, "concmd: ooooops!");
5111 concmdAdd
!false(buf
[0..len
]);
5113 concmdAdd
!false(s
[idx
..idx
+1]);
5116 concmdAdd
!false("\"");
5123 static if (immediate
) bool res
= false;
5126 while (idx
< args
.length
) {
5127 string a
= args
[idx
++];
5128 if (a
.length
== 0) continue;
5129 if (a
== "--") break; // no more
5130 if (a
== "++" || a
== "+--") {
5131 // normal parsing, remove this
5133 foreach (immutable c
; idx
+1..args
.length
) args
[c
-1] = args
[c
];
5138 scope(exit
) ensureCmd
= true;
5141 while (idx
< args
.length
) {
5144 if (a
[0] == '+') break;
5149 foreach (immutable c
; idx
..args
.length
) args
[xidx
+c
-idx
] = args
[c
];
5150 args
.length
-= idx
-xidx
;
5152 // execute it immediately if we're asked to do so
5153 static if (immediate
) {
5154 if (concmdbufpos
> ocbpos
) {
5156 conProcessQueue(256*1024);
5157 ocbpos
= concmdbufpos
;
5163 debug(concmd_procargs
) {
5164 import core
.stdc
.stdio
: snprintf
;
5165 import core
.sys
.posix
.unistd
: STDERR_FILENO
, write
;
5166 if (concmdbufpos
> ocbpos
) {
5167 write(STDERR_FILENO
, "===\n".ptr
, 4);
5168 write(STDERR_FILENO
, concmdbuf
.ptr
+ocbpos
, concmdbufpos
-ocbpos
);
5169 write(STDERR_FILENO
, "\n".ptr
, 1);
5171 foreach (immutable aidx
, string a
; args
) {
5173 auto len
= snprintf(buf
.ptr
, buf
.length
, "%u: ", cast(uint)aidx
);
5174 write(STDERR_FILENO
, buf
.ptr
, len
);
5175 write(STDERR_FILENO
, a
.ptr
, a
.length
);
5176 write(STDERR_FILENO
, "\n".ptr
, 1);
5180 static if (immediate
) return res
; else return (concmdbufpos
> ocbpos
);
5184 // ////////////////////////////////////////////////////////////////////////// //
5186 static if (__traits(compiles
, (){import iv
.vfs
;}())) {
5187 enum CmdConHasVFS
= true;
5190 enum CmdConHasVFS
= false;
5191 import std
.stdio
: File
;
5193 private int xindexof (const(char)[] s
, const(char)[] pat
, int stpos
=0) nothrow @trusted @nogc {
5194 if (pat
.length
== 0 || pat
.length
> s
.length
) return -1;
5195 if (stpos
< 0) stpos
= 0;
5196 if (s
.length
> int.max
/4) s
= s
[0..int.max
/4];
5197 while (stpos
< s
.length
) {
5198 if (s
.length
-stpos
< pat
.length
) break;
5199 if (s
.ptr
[stpos
] == pat
.ptr
[0]) {
5200 if (s
.ptr
[stpos
..stpos
+pat
.length
] == pat
[]) return stpos
;
5207 // return "" from this to prevent script loading (NOT null)
5208 // return `null` to go on
5209 // otherwise, loader will use returned path
5210 __gshared string
delegate (ConString fname
) conExecPathCheck
;
5212 shared static this () {
5213 conRegFunc
!((ConString fname
, bool silent
=false) {
5215 string newfname
= null;
5216 if (conExecPathCheck
!is null) {
5217 newfname
= conExecPathCheck(fname
);
5218 if (newfname
!is null && newfname
.length
== 0) {
5220 throw new Exception("invalid script path");
5223 static if (CmdConHasVFS
) {
5224 auto fl
= VFile(newfname
is null ? fname
: newfname
);
5226 auto fl
= File(newfname
is null ? fname
.idup
: newfname
);
5229 if (sz
> 1024*1024*64) throw new Exception("script file too big");
5231 enum AbortCmd
= "!!abort!!";
5232 auto s
= new char[](cast(uint)sz
);
5234 scope(exit
) delete anchor
;
5235 static if (CmdConHasVFS
) {
5239 while (tbuf
.length
) {
5240 auto rd
= fl
.rawRead(tbuf
);
5241 if (rd
.length
== 0) throw new Exception("read error");
5242 tbuf
= tbuf
[rd
.length
..$];
5245 if (s
.xindexof(AbortCmd
) >= 0) {
5246 auto apos
= s
.xindexof(AbortCmd
);
5248 if (s
.length
-apos
<= AbortCmd
.length || s
[apos
+AbortCmd
.length
] <= ' ') {
5250 // it should be the first command, not commented
5252 while (pos
> 0 && s
[pos
-1] != '\n') {
5253 if (s
[pos
-1] != ' ' && s
[pos
-1] != '\t') { good
= false; break; }
5256 if (good
) { s
= s
[0..apos
]; break; }
5259 apos
= s
.xindexof(AbortCmd
, apos
+1);
5264 } catch (Exception e
) {
5265 if (!silent
) conwriteln("ERROR loading script \"", fname
, "\"");
5267 })("exec", "execute console script (name [silent_failure_flag])");