egra: agg mini clipping bugfix; some fixes in widget rendering (buttons, etc.)
[iv.d.git] / cmdcon / core.d
blobae1b3424212504891f9591735061adab1d941dbf
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
21 * specifiers:
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*/;
35 private:
36 import iv.alice;
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) {
63 import iv.atomic;
64 private __gshared AtomicSpinLockRecursive consoleSpinLock;
65 } else {
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 // ////////////////////////////////////////////////////////////////////////// //
84 // ascii only
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
89 if (__ctfe) {
90 if (c0 == s1[idx]) continue;
91 } else {
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
97 if (__ctfe) {
98 if (c0 != (s1[idx]|0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
99 } else {
100 if (c0 != (s1.ptr[idx]|0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
103 return true;
107 // ////////////////////////////////////////////////////////////////////////// //
109 version(test_cbuf)
110 enum ConBufSize = 64;
111 else
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';
127 version(test_cbuf) {
128 cbufcursize = 32;
129 cbufmaxsize = 256;
130 } else {
131 cbufcursize = 8192;
133 cbuf = cast(char*)malloc(cbufcursize);
134 if (cbuf is null) assert(0, "cmdcon: out of memory!"); // unlikely case
135 cbuf[0..cbufcursize] = 0;
136 cbuf[0] = '\n';
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)*/ {
149 import std.traits;
150 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
151 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
156 /// multithread lock
157 public void consoleLock() () nothrow @trusted @nogc {
158 /*version(aliced)*/ pragma(inline, true);
159 version(iv_cmdcon_use_spinlock) {
160 consoleSpinLock.lock();
161 } else {
162 version(aliced) {
163 consoleLocker.lock();
164 } else {
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();
176 } else {
177 version(aliced) {
178 consoleLocker.unlock();
179 } else {
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;
189 } else {
190 // multithread lock
191 public void consoleWriteLock() () nothrow @trusted @nogc {
192 version(aliced) pragma(inline, true);
193 version(aliced) {
194 consoleWriteLocker.lock();
195 } else {
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);
204 version(aliced) {
205 consoleWriteLocker.unlock();
206 } else {
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) {
227 version(Windows) {
228 //fuck
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) {
234 DWORD ww;
235 usize xpos = 0;
236 while (xpos < chrs.length) {
237 usize epos = xpos;
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);
241 xpos = epos+1;
244 } else {
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;
249 while (left) {
250 uint wr = (left < 0x1fff_ffff ? cast(uint)left : 0x1fff_ffff);
251 immutable res = write(fd, p, wr);
252 if (wr < 0) {
253 import core.stdc.errno : errno, EINTR;
254 if (errno == EINTR) continue;
255 break;
257 p += res;
258 left -= res;
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;
276 version(test_cbuf) {
277 auto newsz = cbufcursize+13;
278 } else {
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);
284 if (nbuf !is null) {
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); }
287 if (np == 0) {
288 // yay, we can do some trickery here
289 np = cbuftail+1; // yep, that easy
290 } else {
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;
296 cbufcursize = newsz;
297 cbuf = nbuf;
298 removelines = false;
299 version(test_cbuf) { import core.stdc.stdio : stderr, fprintf; stderr.fprintf(" cbufhead=%u; cbuftail=%u\n", cbufhead, cbuftail); }
300 assert(np < cbufcursize);
303 if (removelines) {
304 for (;;) {
305 char och = cbuf[cbufhead];
306 cbufhead = (cbufhead+1)%cbufcursize;
307 if (cbufhead == np || och == '\n') break;
311 cbuf[np] = ch;
312 cbuftail = np;
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 {
322 static struct Line {
323 nothrow @trusted @nogc:
324 private:
325 int h, t; // head and tail, to check validity
326 int sp = -1, ep;
328 public:
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:
340 private:
341 int h, t; // head and tail, to check validity
342 int pos; // position of prev line
343 Line line;
345 void toLineStart () {
346 line.ep = pos;
347 while (pos != cbufhead) {
348 int p = (pos+cbufcursize-1)%cbufcursize;
349 if (cbuf[p] == '\n') break;
350 pos = p;
352 line.sp = pos;
353 line.h = h;
354 line.t = t;
357 public:
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); }
361 void popFront () {
362 if (pos < 0 || pos == h || h != cbufhead || t != cbuftail) { line = Line.init; h = t = pos = -1; return; }
363 pos = (pos+cbufcursize-1)%cbufcursize;
364 toLineStart();
368 Range res;
369 res.h = cbufhead;
370 res.pos = res.t = cbuftail;
371 if (cbuf[res.pos] != '\n') res.pos = (res.pos+1)%cbufcursize;
372 res.toLineStart();
373 //{ import std.stdio; writeln("pos=", res.pos, "; head=", res.h, "; tail=", res.t, "; llen=", res.line.length, "; [", res.line, "]"); }
374 return res;
378 // ////////////////////////////////////////////////////////////////////////// //
379 version(unittest) public void conbufDump () {
380 import std.stdio;
381 int pp = cbufhead;
382 stdout.writeln("==========================");
383 for (;;) {
384 if (cbuf[pp] == '\n') stdout.write('|');
385 stdout.write(cbuf[pp]);
386 if (pp == cbuftail) {
387 if (cbuf[pp] != '\n') stdout.write('\n');
388 break;
390 pp = (pp+1)%cbufcursize;
392 //foreach (/+auto+/ s; conbufLinesRev) stdout.writeln(s, "|");
396 // ////////////////////////////////////////////////////////////////////////// //
397 version(test_cbuf) unittest {
398 conbufDump();
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));
408 conbufDump();
413 // ////////////////////////////////////////////////////////////////////////// //
414 // writer
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;
439 } else {
440 alias XUQQ = T;
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 {
455 if (maxwdt < 0) {
456 if (maxwdt == maxwdt.min) ++maxwdt; // alas
457 maxwdt = -(maxwdt);
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]);
466 str = str[1..$];
467 --wdt;
471 if (str.length < wdt) {
472 // '+' means "center"
473 if (signw == '+') {
474 foreach (immutable _; 0..(wdt-str.length)/2) cwrxputch(lchar);
475 } else if (signw != '-') {
476 foreach (immutable _; 0..wdt-str.length) cwrxputch(lchar);
479 cwrxputch(str);
480 if (str.length < wdt) {
481 // '+' means "center"
482 if (signw == '+') {
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 {
492 if (str is null) {
493 cwrxputstr!cutsign("", signw, lchar, rchar, wdt, maxwdt);
494 } else {
495 auto end = str;
496 while (*end) ++end;
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 {
508 char[32] buf = ' ';
510 static if (is(TT == shared)) {
511 import core.atomic;
512 alias T = XUQQ!TT;
513 T n = atomicLoad(nn);
514 } else {
515 alias T = XUQQ!TT;
516 T n = 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)) {
530 enum neg = false;
531 } else {
532 bool neg = (n < 0);
533 if (neg) n = -(n);
536 int bpos = buf.length;
537 do {
538 //if (bpos == 0) assert(0, "internal printer error");
539 buf.ptr[--bpos] = cast(char)('0'+n%10);
540 n /= 10;
541 } while (n != 0);
542 if (neg) {
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 {
551 char[32] buf = ' ';
553 static if (is(TT == shared)) {
554 import core.atomic;
555 alias T = XUQQ!TT;
556 T n = atomicLoad(nn);
557 } else {
558 alias T = XUQQ!TT;
559 T n = 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)) {
573 enum neg = false;
574 } else {
575 bool neg = (n < 0);
576 if (neg) n = -(n);
579 int bpos = buf.length;
580 do {
581 //if (bpos == 0) assert(0, "internal printer error");
582 immutable ubyte b = n&0x0f;
583 n >>= 4;
584 if (b < 10) buf.ptr[--bpos] = cast(char)('0'+b); else buf.ptr[--bpos] = cast(char)((upcase ? 'A' : 'a')+(b-10));
585 } while (n != 0);
586 if (neg) {
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)) {
598 import core.atomic;
599 alias T = XUQQ!TT;
600 T n = atomicLoad(nn);
601 } else {
602 alias T = XUQQ!TT;
603 T n = nn;
606 static char* buf;
607 static usize buflen = 0;
608 char[256] fmtstr;
609 int fspos = 0;
611 if (buf is null) {
612 buflen = 256;
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); }
620 char[24] buf = void;
621 int bpos = buf.length;
622 do {
623 buf.ptr[--bpos] = cast(char)('0'+n%10);
624 n /= 10;
625 } while (n != 0);
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++] = '%';
632 if (!simple) {
633 if (wdt) {
634 if (signw == '-') fmtstr.ptr[fspos++] = '-';
635 putNum(wdt);
637 if (maxwdt) {
638 fmtstr.ptr[fspos++] = '.';
639 putNum(maxwdt);
641 fmtstr.ptr[fspos++] = 'g';
642 maxwdt = 0;
643 } else {
644 fmtstr.ptr[fspos++] = 'g';
646 fmtstr.ptr[fspos++] = '\x00';
647 //{ import core.stdc.stdio; printf("<%s>", fmtstr.ptr); }
649 for (;;) {
650 import core.stdc.stdio : snprintf;
651 auto plen = snprintf(buf, buflen, fmtstr.ptr, cast(double)n);
652 if (plen >= buflen) {
653 buflen = plen+2;
654 buf = cast(char*)realloc(buf, buflen);
655 if (buf is null) assert(0, "out of memory");
656 } else {
657 if (lchar != ' ') {
658 foreach (ref ch; buf[0..plen]) {
659 if (ch != ' ') break;
660 ch = lchar;
663 if (rchar != ' ') {
664 foreach_reverse (ref ch; buf[0..plen]) {
665 if (ch != ' ') break;
666 ch = rchar;
669 //{ import core.stdc.stdio; printf("<{%s}>", buf); }
670 cwrxputstr!true(buf[0..plen], signw, lchar, rchar, wdt, maxwdt);
671 return;
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)) {
679 import core.atomic;
680 alias T = XUQQ!TT;
681 T n = atomicLoad(nn);
682 } else {
683 alias T = XUQQ!TT;
684 T n = nn;
687 foreach (string mname; __traits(allMembers, T)) {
688 if (n == __traits(getMember, T, mname)) {
689 cwrxputstr!false(mname, signw, lchar, rchar, wdt, maxwdt);
690 return;
693 static if (__traits(isUnsigned, T)) {
694 cwrxputint!long(cast(long)n, signw, lchar, rchar, wdt, maxwdt);
695 } else {
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
709 * specifiers:
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;
725 usize tlen;
727 struct Writer {
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';
741 if (c <= 0x7F) {
742 s.ptr[0] = cast(char)c;
743 return s[0..1];
744 } else {
745 if (c <= 0x7FF) {
746 s.ptr[0] = cast(char)(0xC0|(c>>6));
747 s.ptr[1] = cast(char)(0x80|(c&0x3F));
748 return s[0..2];
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));
753 return s[0..3];
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));
759 return s[0..4];
760 } else {
761 s[0..3] = "<?>";
762 return s[0..3];
767 void parseInt(bool allowsign) (ref IntNum n) nothrow @trusted @nogc {
768 n.sign = ' ';
769 n.aval = 0;
770 n.leadingZero = false;
771 if (fmt.length == 0) return;
772 static if (allowsign) {
773 if (fmt.ptr[0] == '-' || fmt.ptr[0] == '+') {
774 n.sign = fmt.ptr[0];
775 fmt = fmt[1..$];
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
783 fmt = fmt[1..$];
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]);
796 fmt = fmt[lplen..$];
798 if (fmt.length >= 2 && fmt.ptr[0] == '%' && fmt.ptr[1] == '%') {
799 cwrxputch("%");
800 fmt = fmt[2..$];
801 continue;
803 break;
807 if (fmt.length == 0) return;
808 consoleWriteLock();
809 scope(exit) consoleWriteUnlock();
810 auto fmtanchor = fmt;
811 IntNum wdt, maxlen;
812 char mode = ' ';
814 ConString smpDelim = null;
815 ConString fmtleft = null;
817 argloop: foreach (immutable argnum, /*auto*/ att; A) {
818 alias at = XUQQ!att;
819 // skip literal part of the format string
820 skipLitPart();
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; }
826 fmt = fmt[1..$];
827 parseInt!true(wdt);
828 if (fmt.length && fmt[0] == '.') {
829 fmt = fmt[1..$];
830 parseInt!false(maxlen);
831 } else {
832 maxlen.sign = ' ';
833 maxlen.aval = 0;
834 maxlen.leadingZero = false;
836 if (fmt.length == 0) { cwrxputch("<stray-percent-in-conprintf>"); break argloop; }
837 mode = fmt[0];
838 fmt = fmt[1..$];
839 switch (mode) {
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;
846 fmt = null;
847 break;
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;
852 usize epos = 0;
853 while (epos < fmt.length && fmt[epos] != '>') ++epos;
854 smpDelim = fmt[0..epos];
855 if (epos < fmt.length) ++epos;
856 fmtleft = fmt[epos..$];
857 fmt = null;
858 break;
859 case 's': // process as simple string
860 break;
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);
865 continue argloop;
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);
868 continue argloop;
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);
876 } else {
877 cwrxputint(cast(ulong)args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
879 continue argloop;
880 } else {
881 break;
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) ||
890 is(at == char))
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);
894 } else {
895 cwrxputch("<cannot-charprint-");
896 cwrxputch(at.stringof);
897 cwrxputch("in-conprintf>");
899 continue argloop;
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);
917 } else {
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)) {
921 // pointers
922 cwrxputhex(cast(usize)args[argnum], (mode == 'X'), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
923 } else {
924 cwrxputch("<cannot-hexprint-");
925 cwrxputch(at.stringof);
926 cwrxputch("in-conprintf>");
928 continue argloop;
929 case 'f':
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);
939 } else {
940 cwrxputch("<cannot-floatprint-");
941 cwrxputch(at.stringof);
942 cwrxputch("in-conprintf>");
944 continue argloop;
945 default:
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]);
950 } else {
951 cwrxputch(mode);
953 cwrxputch(")>");
954 break argloop;
956 } else {
957 if (smpDelim.length) cwrxputch(smpDelim);
958 wdt.sign = maxlen.sign = ' ';
959 wdt.aval = maxlen.aval = 0;
960 wdt.leadingZero = maxlen.leadingZero = false;
962 // print as string
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)) {
979 // pointers
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);
983 } else {
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)) {
989 // dynamic string
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)[])) {
992 // strings
993 cwrxputstr!false(args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
994 } else {
995 // alas
996 try {
997 import std.format : formatValue, singleSpec;
998 tlen = 0;
999 scope Writer wrt;
1000 scope spec = singleSpec("%s");
1001 formatValue(wrt, args[argnum], spec);
1002 if (tlen > tmp.length) {
1003 cwrxputch("<error-formatting-in-conprintf>");
1004 } else {
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>");
1013 if (fmt is null) {
1014 if (fmtleft.length) cwrxputch(fmtleft);
1017 if (fmt.length) {
1018 skipLitPart();
1019 if (fmt.length) {
1020 assert(fmt[0] == '%');
1021 if (fmt.length == 1) {
1022 cwrxputch("<stray-percent-in-conprintf>");
1023 } else {
1024 fmt = fmt[1..$];
1025 parseInt!true(wdt);
1026 if (fmt.length && fmt[0] == '.') {
1027 fmt = fmt[1..$];
1028 parseInt!false(maxlen);
1030 if (fmt.length == 0) {
1031 cwrxputch("<stray-percent-in-conprintf>");
1032 } else {
1033 mode = fmt[0];
1034 fmt = fmt[1..$];
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);
1041 } else {
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]);
1046 } else {
1047 cwrxputch(mode);
1049 cwrxputch(")>");
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() () {
1068 usize pos;
1069 char[] res;
1070 usize respos;
1071 //string simpledelim;
1072 char[] 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;
1081 res[respos++] = ch;
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); }
1096 char[24] buf;
1097 int bpos = buf.length;
1098 do {
1099 static if (hex) {
1100 buf[--bpos] = "0123456789abcdef"[n&0x0f];
1101 n /= 16;
1102 } else {
1103 buf[--bpos] = cast(char)('0'+n%10);
1104 n /= 10;
1106 --minlen;
1107 } while (n != 0 || minlen > 0);
1108 putRes(buf[bpos..$]);
1111 int parseNum (ref char sign, ref bool leadzero) {
1112 sign = ' ';
1113 leadzero = false;
1114 if (pos >= fmt.length) return 0;
1115 if (fmt[pos] == '-' || fmt[pos] == '+') sign = fmt[pos++];
1116 if (pos >= fmt.length) return 0;
1117 int res = 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';
1120 return res;
1123 void processUntilFSp () {
1124 while (pos < fmt.length) {
1125 usize epos = pos;
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;
1129 ++epos;
1131 if (epos > pos) {
1132 putRes("cwrxputch(`");
1133 putRes(fmt[pos..epos]);
1134 putRes("`);\n");
1135 pos = epos;
1136 if (pos >= fmt.length) break;
1138 if (fmt[pos] != '%') {
1139 putRes("cwrxputch('\\x");
1140 putNum!true(cast(ubyte)fmt[pos], 2);
1141 putRes("');\n");
1142 ++pos;
1143 continue;
1145 if (fmt.length-pos < 2 || fmt[pos+1] == '%') { putRes("cwrxputch('%');\n"); pos += 2; continue; }
1146 return;
1150 bool simples = false;
1151 bool putsimpledelim = false;
1152 char lchar=' ', rchar=' ';
1153 char signw = ' ';
1154 bool leadzerow = false;
1155 int wdt, maxwdt;
1156 char fmtch;
1158 argloop: foreach (immutable argnum, /*auto*/ att; A) {
1159 alias at = XUQQ!att;
1160 if (!simples) {
1161 processUntilFSp();
1162 if (pos >= fmt.length) assert(0, "out of format specifiers for arguments");
1163 assert(fmt[pos] == '%');
1164 ++pos;
1165 if (pos < fmt.length && fmt[pos] == '!') { ++pos; break; } // skip rest
1166 if (pos < fmt.length && fmt[pos] == '|') {
1167 ++pos;
1168 simples = true;
1169 } else if (pos < fmt.length && fmt[pos] == '<') {
1170 ++pos;
1171 simples = true;
1172 auto ep = pos;
1173 while (ep < fmt.length) {
1174 if (fmt[ep] == '>') break;
1175 if (fmt[ep] == '\\') ++ep;
1176 ++ep;
1178 if (ep >= fmt.length) assert(0, "invalid format string");
1179 simpledelimpos = 0;
1180 if (ep-pos > 0) {
1181 bool hasQuote = false;
1182 foreach (char ch; fmt[pos..ep]) if (ch == '\\' || (ch < ' ' && ch != '\n' && ch != '\t') || ch >= 127 || ch == '`') { hasQuote = true; break; }
1183 if (!hasQuote) {
1184 putSD("cwrxputch(`");
1185 putSD(fmt[pos..ep]);
1186 putSD("`);\n");
1187 } else {
1188 //FIXME: get rid of char-by-char processing!
1189 putSD("cwrxputch(\"");
1190 while (pos < ep) {
1191 char ch = fmt[pos++];
1192 if (ch == '\\') ch = fmt[pos++];
1193 if (ch == '\\' || ch < ' ' || ch >= 127 || ch == '"') {
1194 putSD("\\x");
1195 putSD("0123456789abcdef"[ch>>4]);
1196 putSD("0123456789abcdef"[ch&0x0f]);
1197 } else {
1198 putSD(ch);
1201 putSD("\");\n");
1204 pos = ep+1;
1205 } else {
1206 lchar = rchar = ' ';
1207 bool lrset = false;
1208 if (pos < fmt.length && fmt[pos] == '~') {
1209 lrset = true;
1210 if (fmt.length-pos < 2) assert(0, "invalid format string");
1211 lchar = fmt[pos+1];
1212 pos += 2;
1213 if (pos < fmt.length && fmt[pos] == '~') {
1214 if (fmt.length-pos < 2) assert(0, "invalid format string");
1215 rchar = fmt[pos+1];
1216 pos += 2;
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] == '.') {
1223 char mws;
1224 bool lzw;
1225 ++pos;
1226 maxwdt = parseNum(mws, lzw);
1227 if (mws == '-') maxwdt = -maxwdt;
1228 } else {
1229 maxwdt = 0;
1231 if (pos >= fmt.length) assert(0, "invalid format string");
1232 fmtch = fmt[pos++];
1235 if (simples) {
1236 lchar = rchar = signw = ' ';
1237 leadzerow = false;
1238 wdt = maxwdt = 0;
1239 fmtch = 's';
1240 if (putsimpledelim && simpledelimpos > 0) {
1241 putRes(simpledelim[0..simpledelimpos]);
1242 } else {
1243 putsimpledelim = true;
1246 switch (fmtch) {
1247 case 's':
1248 case 'd': // oops
1249 case 'u': // oops
1250 static if (is(at == char)) {
1251 putRes("cwrxputchar(args[");
1252 putNum(argnum);
1253 putRes("]");
1254 } else static if (is(at == wchar) || is(at == dchar)) {
1255 putRes("import std.conv : to; cwrxputstr(to!string(args[");
1256 putNum(argnum);
1257 putRes("])");
1258 } else static if (is(at == bool)) {
1259 putRes("cwrxputstr((args[");
1260 putNum(argnum);
1261 putRes("] ? `true` : `false`)");
1262 } else static if (is(at == enum)) {
1263 putRes("cwrxputenum(args[");
1264 putNum(argnum);
1265 putRes("]");
1266 } else static if (is(at == float) || is(at == double) || is(at == real)) {
1267 putRes("cwrxputfloat(args[");
1268 putNum(argnum);
1269 putRes("], true");
1270 } else static if (is(at : const(char)[])) {
1271 putRes("cwrxputstr(args[");
1272 putNum(argnum);
1273 putRes("]");
1274 } else static if (is(at : T*, T)) {
1275 //putRes("cwrxputch(`0x`); ");
1276 static if (is(T == char)) {
1277 putRes("cwrxputstrz(args[");
1278 putNum(argnum);
1279 putRes("]");
1280 } else {
1281 if (wdt < (void*).sizeof*2) { lchar = '0'; wdt = cast(int)((void*).sizeof)*2; signw = ' '; }
1282 putRes("cwrxputhex(cast(usize)args[");
1283 putNum(argnum);
1284 putRes("], false");
1286 } else static if (is(at : long)) {
1287 putRes("cwrxputint(args[");
1288 putNum(argnum);
1289 putRes("]");
1290 } else {
1291 putRes("import std.conv : to; cwrxputstr(to!string(args[");
1292 putNum(argnum);
1293 putRes("])");
1295 break;
1296 case 'x':
1297 static if (is(at == char) || is(at == wchar) || is(at == dchar)) {
1298 putRes("cwrxputhex(cast(uint)args[");
1299 putNum(argnum);
1300 putRes("], false");
1301 } else static if (is(at == bool)) {
1302 putRes("cwrxputstr((args[");
1303 putNum(argnum);
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[");
1310 putNum(argnum);
1311 putRes("], false");
1312 } else static if (is(at : long)) {
1313 putRes("cwrxputhex(args[");
1314 putNum(argnum);
1315 putRes("], false");
1316 } else {
1317 assert(0, "can't print '"~at.stringof~"' as hex");
1319 break;
1320 case 'X':
1321 static if (is(at == char) || is(at == wchar) || is(at == dchar)) {
1322 putRes("cwrxputhex(cast(uint)args[");
1323 putNum(argnum);
1324 putRes("], true");
1325 } else static if (is(at == bool)) {
1326 putRes("cwrxputstr((args[");
1327 putNum(argnum);
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[");
1334 putNum(argnum);
1335 putRes("], true");
1336 } else static if (is(at : long)) {
1337 putRes("cwrxputhex(args[");
1338 putNum(argnum);
1339 putRes("], true");
1340 } else {
1341 assert(0, "can't print '"~at.stringof~"' as hex");
1343 break;
1344 case 'f':
1345 static if (is(at == float) || is(at == double) || is(at == real)) {
1346 putRes("cwrxputfloat(args[");
1347 putNum(argnum);
1348 putRes("], false");
1349 } else {
1350 assert(0, "can't print '"~at.stringof~"' as float");
1352 break;
1353 default: assert(0, "invalid format specifier: '"~fmtch~"'");
1355 putRes(", '");
1356 putRes(signw);
1357 putRes("', '\\x");
1358 putNum!true(cast(uint)lchar, 2);
1359 putRes("', '\\x");
1360 putNum!true(cast(uint)rchar, 2);
1361 putRes("', ");
1362 putNum(wdt);
1363 putRes(", ");
1364 putNum(maxwdt);
1365 putRes(");\n");
1367 while (pos < fmt.length) {
1368 processUntilFSp();
1369 if (pos >= fmt.length) break;
1370 assert(fmt[pos] == '%');
1371 ++pos;
1372 if (pos < fmt.length && (fmt[pos] == '!' || fmt[pos] == '|')) { ++pos; continue; } // skip rest
1373 if (pos < fmt.length && fmt[pos] == '<') {
1374 // skip it
1375 while (pos < fmt.length) {
1376 if (fmt[pos] == '>') break;
1377 if (fmt[pos] == '\\') ++pos;
1378 ++pos;
1380 ++pos;
1381 continue;
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, "===========");
1391 consoleWriteLock();
1392 scope(exit) consoleWriteUnlock();
1393 mixin(gen());
1398 // ////////////////////////////////////////////////////////////////////////// //
1399 public:
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
1406 } else {
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)
1416 unittest {
1417 class A {
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);
1456 wrflt();
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"));
1469 return false;
1470 }();
1474 version(conwriter_test_dump)
1475 unittest {
1476 int x = 5;
1477 int y = 3;
1478 int z = 15;
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
1485 conbufDump();
1489 // ////////////////////////////////////////////////////////////////////////// //
1490 // command console
1492 /// base console command class
1493 public class ConCommand {
1494 private:
1495 public import std.conv : ConvException, ConvOverflowException;
1496 import std.range;
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)");
1526 public:
1527 alias ArgCompleteCB = void delegate (ConCommand self); /// prototype for argument completion callback
1529 private:
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]; }
1555 public:
1556 string name; ///
1557 string help; ///
1559 this (string aname, string ahelp=null) { name = aname; help = ahelp; } ///
1561 void showHelp () { conwriteln(name, " -- ", help); } ///
1563 /// can throw, yep
1564 /// cmdline doesn't contain command name
1565 void exec (ConString cmdline) {
1566 auto w = getWord(cmdline);
1567 if (w == "?") showHelp;
1570 public:
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; }
1574 static:
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) {
1577 int res = void;
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;
1581 else return -1;
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) {
1594 wbReset();
1595 if (strtemp !is null) *strtemp = false;
1596 usize pos;
1597 while (s.length > 0 && s.ptr[0] <= ' ') s = s[1..$];
1598 if (s.length == 0) return null;
1599 // quoted string?
1600 if (s.ptr[0] == '"' || s.ptr[0] == '\'') {
1601 char qch = s.ptr[0];
1602 s = s[1..$];
1603 pos = 0;
1604 bool hasSpecial = false;
1605 while (pos < s.length && s.ptr[pos] != qch) {
1606 if (s.ptr[pos] == '\\') { hasSpecial = true; break; }
1607 ++pos;
1609 // simple quoted string?
1610 if (!hasSpecial) {
1611 auto res = s[0..pos];
1612 if (pos < s.length) ++pos; // skip closing quote
1613 s = s[pos..$];
1614 return res;
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) {
1623 ++pos;
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;
1635 case 'x': case 'X':
1636 int n = 0;
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);
1643 wbPut(cast(char)n);
1644 break;
1645 default: throw exBadEscChar;
1647 continue;
1649 wbPut(s.ptr[pos++]);
1651 if (pos < s.length) ++pos; // skip closing quote
1652 s = s[pos..$];
1653 return wordBuf;
1654 } else {
1655 // normal word
1656 pos = 0;
1657 while (pos < s.length && s.ptr[pos] > ' ') ++pos;
1658 auto res = s[0..pos];
1659 s = s[pos..$];
1660 return res;
1664 /// parse value of type T.
1665 T parseType(T) (ref ConString s) {
1666 import std.utf : byCodeUnit;
1667 alias UT = XUQQ!T;
1668 static if (is(UT == enum)) {
1669 // enum
1670 auto w = getWord(s);
1671 // case-sensitive
1672 foreach (string mname; __traits(allMembers, UT)) {
1673 if (mname == w) return __traits(getMember, UT, mname);
1675 // case-insensitive
1676 foreach (string mname; __traits(allMembers, UT)) {
1677 if (strEquCI(mname, w)) return __traits(getMember, UT, mname);
1679 // integer
1680 if (w.length < 1) throw exBadEnum;
1681 auto ns = w;
1682 try {
1683 if (w[0] == '-') {
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);
1689 } else {
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) {}
1697 throw exBadEnum;
1698 } else static if (is(UT == bool)) {
1699 // boolean
1700 auto w = getWord(s);
1701 if (w is null) throw exNoArg;
1702 bool good = false;
1703 auto res = parseBool(w, true, &good);
1704 if (!good) throw exBadBool;
1705 return res;
1706 } else static if ((isIntegral!UT || isFloatingPoint!UT) && !is(UT == enum)) {
1707 // number
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
1712 if (goodbool) {
1713 return cast(UT)(bv ? 1 : 0);
1714 } else {
1715 auto ss = w.byCodeUnit;
1716 auto res = parseNum!UT(ss);
1717 if (!ss.empty) throw exBadNum;
1718 return res;
1720 } else static if (is(UT : ConString)) {
1721 // string
1722 bool stemp = false;
1723 auto w = getWord(s);
1724 if (w is null) throw exNoArg;
1725 if (s.length && s.ptr[0] > 32) throw exBadStr;
1726 if (stemp) {
1727 // temp storage was used
1728 static if (is(UT == string)) return w.idup; else return w.dup;
1729 } else {
1730 // no temp storage was used
1731 static if (is(UT == ConString)) return w;
1732 else static if (is(UT == string)) return w.idup;
1733 else return w.dup;
1735 } else static if (is(UT : char)) {
1736 // char
1737 bool stemp = false;
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;
1741 return w.ptr[0];
1742 } else {
1743 throw exBadArgType;
1747 /// parse boolean value
1748 public static bool parseBool (ConString s, bool allowNumbers=true, bool* goodval=null) nothrow @trusted @nogc {
1749 char[5] tbuf;
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;
1754 usize pos = 0;
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]) {
1760 case "y": case "t":
1761 case "yes": case "tan":
1762 case "true": case "on":
1763 if (goodval !is null) *goodval = true;
1764 return true;
1765 case "1": case "-1": case "42":
1766 if (allowNumbers) {
1767 if (goodval !is null) *goodval = true;
1768 return true;
1770 break;
1771 case "n": case "f":
1772 case "no": case "ona":
1773 case "false": case "off":
1774 if (goodval !is null) *goodval = true;
1775 return false;
1776 case "0":
1777 if (allowNumbers) {
1778 if (goodval !is null) *goodval = true;
1779 return false;
1781 break;
1782 default: break;
1784 return false;
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.
1792 * Params:
1793 * T = result type
1794 * s = input range; will be modified
1796 * Returns:
1797 * parsed number
1799 * Throws:
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;
1804 uint base = 10;
1805 ulong num = 0;
1806 static if (isSigned!T) bool neg = false;
1807 // skip spaces
1808 while (!s.empty) {
1809 if (s.front > 32) break;
1810 s.popFront();
1812 if (s.empty) throw exBadInt;
1813 // check for sign
1814 switch (s.front) {
1815 case '+': // it's ok
1816 s.popFront();
1817 break;
1818 case '-':
1819 static if (isSigned!T) {
1820 neg = true;
1821 s.popFront();
1822 break;
1823 } else {
1824 throw exBadInt;
1826 default: // do nothing
1828 if (s.empty) throw exBadInt;
1829 // check for various bases
1830 if (s.front == '0') {
1831 s.popFront();
1832 if (s.empty) return cast(T)0;
1833 auto ch = s.front;
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;
1839 gotbase:
1840 s.popFront();
1841 goto checkfirstdigit;
1842 default:
1843 if (ch != '_' && digit(ch, base) < 0) throw exBadInt;
1844 break;
1846 } else {
1847 // no base specification; we want at least one digit
1848 checkfirstdigit:
1849 if (s.empty || digit(s.front, base) < 0) throw exBadInt;
1851 // parse number
1852 // we already know that the next char is valid
1853 bool needDigit = false;
1854 do {
1855 auto ch = s.front;
1856 int d = digit(ch, base);
1857 if (d < 0) {
1858 if (needDigit) throw exBadInt;
1859 if (ch != '_') break;
1860 needDigit = true;
1861 } else {
1862 // funny overflow checks
1863 auto onum = num;
1864 if ((num *= base) < onum) throw exIntOverflow;
1865 if ((num += d) < onum) throw exIntOverflow;
1866 needDigit = false;
1868 s.popFront();
1869 } while (!s.empty);
1870 if (needDigit) throw exBadInt;
1871 // check underflow and overflow
1872 static if (isSigned!T) {
1873 long n = cast(long)num;
1874 if (neg) {
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);
1878 } else {
1879 if (num >= 0x8000_0000_0000_0000uL) throw exIntOverflow;
1881 if (n < T.min || n > T.max) throw exIntOverflow;
1882 return cast(T)n;
1883 } else {
1884 if (num < T.min || num > T.max) throw exIntOverflow;
1885 return cast(T)num;
1889 /** parse number.
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.
1894 * Params:
1895 * T = result type
1896 * s = input range; will be modified
1898 * Returns:
1899 * parsed number
1901 * Throws:
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);
1907 } else {
1908 while (!s.empty) {
1909 if (s.front > 32) break;
1910 s.popFront();
1912 import std.conv : stcparse = parse;
1913 return stcparse!T(s);
1917 public static bool checkHelp (scope ConString s) {
1918 usize pos = 0;
1919 while (pos < s.length && s.ptr[pos] <= 32) ++pos;
1920 if (pos == s.length || s.ptr[pos] != '?') return false;
1921 ++pos;
1922 while (pos < s.length && s.ptr[pos] <= 32) ++pos;
1923 return (pos >= s.length);
1926 public static bool hasArgs (scope ConString s) {
1927 usize pos = 0;
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);
1946 // need to quote?
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; }
1953 dg("\"");
1954 usize pos = 0;
1955 while (pos < s.length) {
1956 usize end = pos;
1957 while (end < s.length && !isBadStrChar(s.ptr[end])) ++end;
1958 if (end > pos) dg(s[pos..end]);
1959 pos = end;
1960 if (pos >= s.length) break;
1961 dg("\\");
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;
1973 default:
1974 ubyte c = cast(ubyte)(s.ptr[pos-1]);
1975 dg("x");
1976 dg(hexd[c>>4..(c>>4)+1]);
1977 dg(hexd[c&0x0f..(c&0x0f)+1]);
1978 break;
1981 dg("\"");
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);
1996 assert(w == "is");
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);
2012 assert(w is null);
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;
2022 bool ok = false;
2023 try {
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;
2039 try {
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) {
2046 return;
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 // ////////////////////////////////////////////////////////////////////////// //
2147 /// console alias
2148 public final class ConAlias {
2149 private:
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");
2159 usize textmem;
2160 uint textsize;
2161 uint allocsize;
2163 this (const(char)[] atext) @nogc {
2164 set(atext);
2167 ~this () {
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;
2176 // realloc
2177 if (atext.length > allocsize) {
2178 import core.stdc.stdlib : realloc;
2179 uint newsz;
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;
2184 else newsz = 65536;
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
2188 textmem = newmem;
2189 allocsize = newsz;
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 {
2201 None = 0, ///
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)
2204 //TODO:
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 {
2212 protected:
2213 uint mAttrs;
2215 public:
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
2219 private:
2220 PreChangeHookCB hookBeforeChange;
2221 PostChangeHookCB hookAfterChange;
2223 public:
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 {
2228 mAttrs = 0;
2229 foreach (const ConVarAttr a; attrs) {
2230 static if (!any) {
2231 if (a == ConVarAttr.User) continue;
2233 mAttrs |= a;
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)) {
2282 // floats
2283 return cast(T)getDoubleValue;
2284 } else static if (is(T : ConString)) {
2285 // string
2286 static if (is(T == string)) return strval.idup;
2287 else static if (is(T == char[])) return strval.dup;
2288 else return strval;
2289 } else {
2290 // alas
2291 return T.init;
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)) {
2303 // floats
2304 setDoubleValue(cast(double)val);
2305 } else static if (is(T : ConString)) {
2306 static if (is(T == string)) setStrValue(val); else setCCharValue(val);
2307 } else {
2308 static assert(0, "invalid type");
2312 protected:
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 {
2326 alias TT = XUQQ!T;
2327 enum useAtomic = is(T == shared);
2328 T* vptr;
2329 static if (isIntegral!TT) {
2330 TT minv = TT.min;
2331 TT maxv = TT.max;
2332 } else static if (isFloatingPoint!TT) {
2333 TT minv = -TT.max;
2334 TT maxv = TT.max;
2336 static if (!is(TT : ConString)) {
2337 char[256] vbuf;
2338 } else {
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) {
2348 vptr = avptr;
2349 super(aname, ahelp);
2352 static if (isIntegral!TT || isFloatingPoint!TT) {
2353 this (T* avptr, TT aminv, TT amaxv, string aname, string ahelp=null) {
2354 vptr = avptr;
2355 minv = aminv;
2356 maxv = amaxv;
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);
2374 } else {
2375 static if (useAtomic) {
2376 import core.atomic;
2377 atomicStore(*vptr, !prval);
2378 } else {
2379 *vptr = !prval;
2382 if (hookAfterChangeVV !is null) hookAfterChangeVV(this, prval, !prval);
2383 if (hookAfterChange !is null) hookAfterChange(this, cmdline);
2384 return;
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; }
2395 TT oval;
2396 if (hookAfterChangeVV !is null) oval = getv();
2397 if (cvSetter !is null) {
2398 cvSetter(this, val);
2399 } else {
2400 static if (useAtomic) {
2401 import core.atomic;
2402 atomicStore(*vptr, val);
2403 } else {
2404 *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);
2417 import core.atomic;
2418 if (cvGetter !is null) {
2419 return cvGetter(this);
2420 } else {
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)) {
2429 auto v = getv();
2430 foreach (string mname; __traits(allMembers, TT)) {
2431 if (__traits(getMember, TT, mname) == v) return mname;
2433 return "???";
2434 } else static if (is(T : ConString)) {
2435 return getv();
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()));
2441 } else {
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();
2450 return vbuf[0..1];
2451 } else {
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~"); }";
2467 } else {
2468 enum PutVMx = "{ if (cvSetter !is null) cvSetter(this, "~val~"); else *vptr = "~val~"; }";
2472 protected override void setIntValue (ulong v, bool signed) /*nothrow @nogc*/ {
2473 import core.atomic;
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");
2478 return;
2481 // alas
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*/ {
2495 import core.atomic;
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");
2500 return;
2503 // alas
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*/ {
2517 import core.atomic;
2518 static if (is(XUQQ!T == enum)) {
2519 try {
2520 ConString ss = v;
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*/ {
2534 import core.atomic;
2535 static if (is(XUQQ!T == enum)) {
2536 try {
2537 ConString ss = v;
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)) {
2551 auto vx = getv();
2552 foreach (string mname; __traits(allMembers, TT)) {
2553 if (__traits(getMember, TT, mname) == vx) {
2554 conwrite(name);
2555 conwrite(" ");
2556 //writeQuotedString(mname);
2557 conwrite(mname);
2558 conwrite("\n");
2559 return;
2562 //FIXME: doubles?
2563 conwriteln(name, " ", cast(ulong)getv());
2564 } else static if (is(T : ConString)) {
2565 conwrite(name);
2566 conwrite(" ");
2567 writeQuotedString(getv());
2568 conwrite("\n");
2569 } else static if (is(XUQQ!T == char)) {
2570 conwrite(name);
2571 conwrite(" ");
2572 char[1] st = getv();
2573 if (st[0] <= ' ' || st[0] == 127 || st[0] == '"' || st[0] == '\\') {
2574 writeQuotedString(st[]);
2575 } else {
2576 conwrite(`"`);
2577 conwrite(st[]);
2578 conwrite(`"`);
2580 conwrite("\n");
2581 } else static if (is(T == bool)) {
2582 conwriteln(name, " ", (getv() ? "tan" : "ona"));
2583 } else {
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");
2598 cvi.exec("?");
2599 cvs.exec("?");
2600 cvb.exec("?");
2601 cvi.exec("");
2602 cvs.exec("");
2603 cvb.exec("");
2604 cvi.exec("666");
2605 cvs.exec("'fo\to'");
2606 cvi.exec("");
2607 cvs.exec("");
2608 conwriteln("vi=", vi);
2609 conwriteln("vs=[", vs, "]");
2610 cvs.exec("'?'");
2611 cvs.exec("");
2612 cvb.exec("tan");
2613 cvb.exec("");
2614 cvb.exec("ona");
2615 cvb.exec("");
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) {
2625 auto root = start;
2626 for (;;) {
2627 auto child = 2*root+1; // left child
2628 if (child > end) break;
2629 auto swap = root;
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;
2636 root = swap;
2640 auto end = count-1;
2641 // heapify
2642 auto start = (end-1)/2; // parent; always safe, as our array has at least two items
2643 for (;;) {
2644 siftDown(start, end);
2645 if (start-- == 0) break; // as `start` cannot be negative, use this condition
2648 while (end > 0) {
2650 auto tmp = arr.ptr[0];
2651 arr.ptr[0] = arr.ptr[end];
2652 arr.ptr[end] = tmp;
2654 --end;
2655 siftDown(0, 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;
2663 arr ~= v;
2664 if (arr.ptr !is optr) {
2665 import core.memory : GC;
2666 optr = arr.ptr;
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); }
2674 // ascii only
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;
2678 char c1;
2679 foreach (immutable idx, char c0; s0[0..slen]) {
2680 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower()
2681 c1 = s1.ptr[idx];
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;
2688 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 // ////////////////////////////////////////////////////////////////////////// //
2701 import std.range;
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);
2711 } else {
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;
2717 int count = 0;
2718 foreach (const(char)[] mname; rng.save) {
2719 if (mname.length >= pfx.length && strEquCI(mname[0..pfx.length], pfx)) {
2720 if (count == 0) {
2721 bestPfx = mname;
2722 } else {
2723 //if (mname.length < bestPfx.length) bestPfx = bestPfx[0..mname.length];
2724 usize pos = 0;
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;
2733 ++pos;
2735 if (pos < bestPfx.length) bestPfx = bestPfx[0..pos];
2737 ++count;
2740 if (count == 0 || bestPfx.length < pfx.length) { conwriteln(self.name, ": ???"); return; }
2741 foreach (char ch; bestPfx[pfx.length..$]) conAddInputChar(ch);
2742 if (count == 1) {
2743 conAddInputChar(' ');
2744 } else {
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);
2765 import std.file;
2766 import std.path;
2767 string[] res;
2768 string path;
2769 if (pfx.length == 0) {
2770 path = ".";
2771 } else {
2772 if (pfx[$-1] == '/') {
2773 path = pfx;
2774 pfx = null;
2775 } else {
2776 path = pfx.dirName;
2777 pfx = pfx.baseName;
2780 string xxname (string fn, bool asDir=false) {
2781 if (fn == "/") return fn;
2782 if (asDir) {
2783 if (fn == ".") return "./";
2785 if (startsWith(fn, "./")) fn = fn[2..$];
2786 if (asDir) fn ~= "/";
2787 return fn;
2789 try {
2790 foreach (DirEntry de; dirEntries(path, SpanMode.shallow)) {
2791 if (de.baseName.length == 0) continue;
2792 if (de.baseName[0] == '.') continue;
2793 try {
2794 if (de.isFile) {
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;
2808 return (a < b);
2810 if (b[$-1] == '/') return false;
2811 return (a < b);
2813 return res;
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();
2827 if (cfn.length) {
2828 conAddInputChar(ConInputChar.Backspace); // remove "!"
2829 foreach (char ch; cfn) conAddInputChar(ch);
2830 return;
2833 string[] list = cmdconFilesList(cs.idup, isGood);
2834 if (list.length == 0) {
2835 conwriteln(self.name, ": no matching files");
2836 return;
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);
2859 } else {
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
2864 string bestPfx;
2865 int count = 0;
2866 foreach (string mname; __traits(allMembers, T)) {
2867 if (mname.length >= pfx.length && strEquCI(mname[0..pfx.length], pfx)) {
2868 if (count == 0) {
2869 bestPfx = mname;
2870 } else {
2871 //if (mname.length < bestPfx.length) bestPfx = bestPfx[0..mname.length];
2872 usize pos = 0;
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;
2879 ++pos;
2881 if (pos < bestPfx.length) bestPfx = bestPfx[0..pos];
2883 ++count;
2886 if (count == 0 || bestPfx.length < pfx.length) { conwriteln(self.name, ": ???"); return; }
2887 foreach (char ch; bestPfx[pfx.length..$]) conAddInputChar(ch);
2888 if (count == 1) {
2889 conAddInputChar(' ');
2890 } else {
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(' ');
2915 } else {
2916 conwriteln(" toggle?");
2921 /** register console variable with getter and setter.
2923 * Params:
2924 * aname = variable name
2925 * ahelp = help text
2926 * dgg = getter
2927 * dgs = setter
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) {
2935 addName(aname);
2936 auto cv = new ConVar!T(null, aname, ahelp);
2937 cv.setAttrs(attrs);
2938 cv.cvGetter = dgg;
2939 cv.cvSetter = dgs;
2940 cmdlist[aname] = cv;
2941 return cv;
2942 } else {
2943 return null;
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; }`~
2953 `}`~
2954 `if (aname.length > 0) {`~
2955 ` addName(aname);`~
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);`~
2965 ` }`~
2966 ` cmdlist[aname] = cv;`~
2967 ` return cv;`~
2968 `} else {`~
2969 ` return null;`~
2970 `}`;
2973 /** register integral console variable with bounded value.
2975 * Params:
2976 * v = variable symbol
2977 * aminv = minimum value
2978 * amaxv = maximum value
2979 * aname = variable name
2980 * ahelp = help text
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.
2991 * Params:
2992 * v = variable symbol
2993 * aname = variable name
2994 * ahelp = help text
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.
3005 * Params:
3006 * v = variable symbol
3007 * aminv = minimum value
3008 * amaxv = maximum value
3009 * aname = variable name
3010 * ahelp = help text
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.
3021 * Params:
3022 * v = variable symbol
3023 * aminv = minimum value
3024 * amaxv = maximum value
3025 * aname = variable name
3026 * ahelp = help text
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.
3037 * Params:
3038 * v = variable symbol
3039 * aminv = minimum value
3040 * amaxv = maximum value
3041 * aname = variable name
3042 * ahelp = help text
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.
3053 * Params:
3054 * v = variable symbol
3055 * aminv = minimum value
3056 * amaxv = maximum value
3057 * aname = variable name
3058 * ahelp = help text
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.
3068 * Params:
3069 * v = variable symbol
3070 * aminv = minimum value
3071 * amaxv = maximum value
3072 * aname = variable name
3073 * ahelp = help text
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.
3084 * Params:
3085 * v = variable symbol
3086 * aname = variable name
3087 * ahelp = help text
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.
3096 * Params:
3097 * v = variable symbol
3098 * aname = variable name
3099 * ahelp = help text
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.
3110 * Params:
3111 * v = variable symbol
3112 * aname = variable name
3113 * ahelp = help text
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.
3124 * Params:
3125 * v = variable symbol
3126 * aname = variable name
3127 * ahelp = help text
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.
3137 * Params:
3138 * v = variable symbol
3139 * aname = variable name
3140 * ahelp = help text
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 // ////////////////////////////////////////////////////////////////////////// //
3150 // delegate
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 {
3158 ConString cmdline;
3162 /** register console command.
3164 * Params:
3165 * fn = symbol
3166 * aname = variable name
3167 * ahelp = help text
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))) {
3174 auto dg = &fn;
3175 } else {
3176 auto dg = 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; }
3184 Parameters!dg args;
3185 static if (args.length == 0) {
3186 if (hasArgs(cmdline)) {
3187 conwriteln("too many args for command '", name, "'");
3188 } else {
3189 dg();
3191 } else static if (args.length == 1 && is(typeof(args[0]) == ConFuncVA)) {
3192 args[0].cmdline = cmdline;
3193 dg(args);
3194 } else {
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
3200 uint restpos;
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[])) {
3207 auto xidx = idx+1;
3208 if (idx != args.length-1) {
3209 conwriteln("error parsing argument #", xidx, " for command '", name, "'");
3210 return;
3212 while (hasArgs(cmdline)) {
3213 try {
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;
3217 ++xidx;
3218 } catch (ConvException) {
3219 conwriteln("error parsing argument #", xidx, " for command '", name, "'");
3220 return;
3223 arg = (restpos <= rest.length ? rest[0..restpos] : restdyn);
3224 } else {
3225 try {
3226 arg = parseType!(typeof(arg))(cmdline);
3227 } catch (ConvException) {
3228 conwriteln("error parsing argument #", idx+1, " for command '", name, "'");
3229 return;
3232 } else {
3233 static if (!is(defaultArguments[idx] == void)) {
3234 arg = defaultArguments[idx];
3235 } else {
3236 conwriteln("required argument #", idx+1, " for command '", name, "' is missing");
3237 return;
3241 if (hasArgs(cmdline)) {
3242 conwriteln("too many args for command '", name, "'");
3243 return;
3245 //static if (is(ReturnType!dg == void))
3246 dg(args);
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);
3256 addName(aname);
3257 cmdlist[aname] = cf;
3258 return cf;
3259 } else {
3260 return null;
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) {
3294 private:
3295 usize idx;
3297 private:
3298 this (usize stidx) {
3299 static if (type == "all") {
3300 idx = stidx;
3301 } else static if (type == "vars") {
3302 while (stidx < cmdlistSorted.length && (cast(ConVarBase)cmdlist[cmdlistSorted.ptr[stidx]]) is null) ++stidx;
3303 idx = stidx;
3304 } else static if (type == "funcs") {
3305 while (stidx < cmdlistSorted.length && (cast(ConFuncBase)cmdlist[cmdlistSorted.ptr[stidx]]) is null) ++stidx;
3306 idx = stidx;
3307 } else static if (type == "aliases") {
3308 while (stidx < cmdlistSorted.length && (cast(ConAlias)cmdlist[cmdlistSorted.ptr[stidx]]) is null) ++stidx;
3309 idx = 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;
3315 ++stidx;
3317 idx = stidx;
3318 } else {
3319 static assert(0, "wtf?!");
3323 public:
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); }
3329 void popFront () {
3330 if (idx >= cmdlistSorted.length) return;
3331 static if (type == "all") {
3332 //pragma(inline, true);
3333 ++idx;
3334 } else static if (type == "vars") {
3335 ++idx;
3336 while (idx < cmdlistSorted.length && (cast(ConVarBase)cmdlist[cmdlistSorted.ptr[idx]]) is null) ++idx;
3337 } else static if (type == "funcs") {
3338 ++idx;
3339 while (idx < cmdlistSorted.length && (cast(ConFuncBase)cmdlist[cmdlistSorted.ptr[idx]]) is null) ++idx;
3340 } else static if (type == "aliases") {
3341 ++idx;
3342 while (idx < cmdlistSorted.length && (cast(ConAlias)cmdlist[cmdlistSorted.ptr[idx]]) is null) ++idx;
3343 } else static if (type == "archive") {
3344 ++idx;
3345 while (idx < cmdlistSorted.length) {
3346 if (auto v = cast(ConVarBase)cmdlist[cmdlistSorted.ptr[idx]]) {
3347 if (v.attrArchive) break;
3349 ++idx;
3351 } else {
3352 static assert(0, "wtf?!");
3356 return Range!type(0);
3360 // ////////////////////////////////////////////////////////////////////////// //
3361 // thread-safe
3363 /// get console variable value (thread-safe)
3364 public T conGetVar(T=ConString) (ConString s) {
3365 consoleLock();
3366 scope(exit) consoleUnlock();
3367 if (auto cc = s in cmdlist) {
3368 if (auto cv = cast(ConVarBase)(*cc)) return cv.value!T;
3370 return T.init;
3373 /// ditto
3374 public T conGetVar(T=ConString) (ConVarBase v) {
3375 consoleLock();
3376 scope(exit) consoleUnlock();
3377 if (v !is null) return v.value!T;
3378 return T.init;
3382 /// set console variable value (thread-safe)
3383 public void conSetVar(T) (ConString s, T val) {
3384 consoleLock();
3385 scope(exit) consoleUnlock();
3386 if (auto cc = s in cmdlist) {
3387 if (auto cv = cast(ConVarBase)(*cc)) cv.value = val;
3391 /// ditto
3392 public void conSetVar(T) (ConVarBase v, T val) {
3393 consoleLock();
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) {
3401 consoleLock();
3402 scope(exit) consoleUnlock();
3403 if (auto cc = s in cmdlist) {
3404 if (auto cv = cast(ConVarBase)(*cc)) cv.attrReadOnly = true;
3408 /// ditto
3409 public void conSealVar (ConVarBase v) {
3410 consoleLock();
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) {
3418 consoleLock();
3419 scope(exit) consoleUnlock();
3420 if (auto cc = s in cmdlist) {
3421 if (auto cv = cast(ConVarBase)(*cc)) cv.attrReadOnly = false;
3425 /// ditto
3426 public void conUnsealVar (ConVarBase v) {
3427 consoleLock();
3428 scope(exit) consoleUnlock();
3429 if (v !is null) v.attrReadOnly = false;
3433 // ////////////////////////////////////////////////////////////////////////// //
3434 // thread-safe
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
3467 consoleLock();
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;
3474 for (;;) {
3475 if (aliaslevel > 0) {
3476 s = conGetCommandStr(curs);
3477 if (s is null) return;
3478 } else {
3479 curs = null;
3481 auto w = ConCommand.getWord(s);
3482 if (w is null) return;
3483 // "wait" pseudocommand
3484 if (w == "wait") {
3485 waitHit = true;
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;
3491 // comment; skip it
3492 while (curs.length && curs.ptr[0] != '\n') curs = curs[1..$];
3494 if (curs.length) {
3495 // has something to insert
3496 if (conWaitPrepends) {
3497 //ccWaitPrependStr = curs;
3498 //concmdPrepend(curs);
3499 setPrependStr(curs);
3500 } else {
3501 concmdAdd!true(curs); // ensure new command
3504 return;
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)) {
3509 // execute command
3510 while (s.length && s.ptr[0] <= 32) s = s[1..$];
3511 //conwriteln("'", s, "'");
3512 consoleUnlock();
3513 scope(exit) consoleLock();
3514 cmd.exec(s);
3515 } else if (auto ali = cast(ConAlias)(*cobj)) {
3516 // execute alias
3517 //TODO: alias arguments
3518 auto atext = ali.get;
3519 //conwriteln("ALIAS: '", w, "': '", atext, "'");
3520 if (atext.length) {
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);
3525 } else {
3526 conwrite("command ");
3527 ConCommand.writeQuotedString(w);
3528 conwrite(" is of unknown type (internal error)");
3529 conwrite("\n");
3531 } else {
3532 conwrite("command ");
3533 ConCommand.writeQuotedString(w);
3534 conwrite(" not found");
3535 conwrite("\n");
3537 s = curs;
3541 try {
3542 conExecuteInternal(s, 0, 0);
3543 } catch (Exception) {
3544 conwriteln("error executing console command:\n ", s);
3546 return waitHit;
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");
3561 conExecute("bang");
3563 conRegFunc!((ConFuncVA va) {
3564 int idx = 1;
3565 for (;;) {
3566 auto w = ConCommand.getWord(va.cmdline);
3567 if (w is null) break;
3568 conwriteln("#", idx, ": [", w, "]");
3569 ++idx;
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) {
3585 for (;;) {
3586 while (s.length > 0 && s[0] <= 32) s = s[1..$];
3587 if (s.length == 0) return null;
3588 if (s.ptr[0] != ';') break;
3589 s = s[1..$];
3592 usize pos = 0;
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;
3606 void skipLine () {
3607 while (pos < s.length) {
3608 if (s.ptr[pos] == '"' || s.ptr[pos] == '\'') {
3609 skipString();
3610 } else if (s.ptr[pos++] == '\n') {
3611 break;
3616 if (s.ptr[0] == '#') {
3617 skipLine();
3618 if (pos >= s.length) { s = s[$..$]; return null; }
3619 s = s[pos..$];
3620 pos = 0;
3623 while (pos < s.length) {
3624 if (s.ptr[pos] == '"' || s.ptr[pos] == '\'') {
3625 skipString();
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..$];
3629 return res;
3630 } else {
3631 ++pos;
3634 auto res = s[];
3635 s = s[$..$];
3636 return res;
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;
3670 for (;;) {
3671 auto w = getWord(cmdline);
3672 if (w is null) break;
3673 if (needSpace) conwrite(" "); else needSpace = true;
3674 while (w.length) {
3675 usize pos = 0;
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]);
3679 w = w[pos+2..$];
3680 } else if (w.length-pos <= 1) {
3681 conwrite(w);
3682 break;
3683 } else {
3684 // variable name
3685 ConString vname;
3686 if (pos > 0) conwrite(w[0..pos]);
3687 ++pos;
3688 if (w.ptr[pos] == '{') {
3689 w = w[pos+1..$];
3690 pos = 0;
3691 while (pos < w.length && w.ptr[pos] != '}') ++pos;
3692 vname = w[0..pos];
3693 if (pos < w.length) ++pos;
3694 w = w[pos..$];
3695 } else {
3696 w = w[pos..$];
3697 pos = 0;
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')) {
3701 ++pos;
3702 } else {
3703 break;
3706 vname = w[0..pos];
3707 w = w[pos..$];
3709 if (vname.length) {
3710 if (auto cc = vname in cmdlist) {
3711 if (auto cv = cast(ConVarBase)(*cc)) {
3712 auto v = cv.strval;
3713 conwrite(v);
3714 } else {
3715 conwrite("${!");
3716 conwrite(vname);
3717 conwrite("}");
3719 } else {
3720 conwrite("${");
3721 conwrite(vname);
3722 conwrite("}");
3728 conwrite("\n");
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)) {
3743 doMin = false;
3744 for (;;) {
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.");
3749 return;
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; }
3756 if (doGC) {
3757 conwriteln("starting GC collection...");
3758 GC.collect();
3760 if (doMin) {
3761 conwriteln("minimising working memory set...");
3762 GC.minimize();
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];
3786 // alias value
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) {
3794 // existing alias?
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) {
3798 // remove
3799 cmdlist.remove(cast(string)xname); // it is safe to cast here
3800 foreach (immutable idx, string n; cmdlistSorted) {
3801 if (n == xname) {
3802 foreach (immutable c; idx+1..cmdlistSorted.length) cmdlistSorted[c-1] = cmdlistSorted[c];
3803 break;
3806 } else {
3807 // change
3808 ali.set(v);
3810 return;
3811 } else {
3812 // new alias
3813 auto ali = new ConAlias(v);
3814 //conwriteln(xname, ": <", ali.get, ">");
3815 string sname = xname.idup;
3816 addName(sname);
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;
3827 ConVarBase v;
3828 static if (is(T : long) || is(T : double)) {
3829 T* var = new T;
3830 v = new ConVar!T(var, aname, help);
3831 } else static if (is(T : const(char)[])) {
3832 T[] var;
3833 var.length = 1;
3834 v = new ConVar!T(&var[0], aname, help);
3835 } else {
3836 static assert(0, "can't create uservar of type '"~T.stringof~"'");
3838 v.setAttrs(attrs);
3839 v.mAttrs |= ConVarAttr.User;
3840 addName(aname);
3841 cmdlist[aname] = v;
3842 return v;
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;
3849 ConVarBase v;
3850 static if (is(T : long) || is(T : double)) {
3851 T* var = new T;
3852 v = new ConVar!T(var, amin, amax, aname, help);
3853 } else static if (is(T : const(char)[])) {
3854 T[] var;
3855 var.length = 1;
3856 v = new ConVar!T(&var[0], aname, help);
3857 } else {
3858 static assert(0, "can't create uservar of type '"~T.stringof~"'");
3860 c.setAttrs(attrs);
3861 v.mAttrs |= ConVarAttr.User;
3862 addName(aname);
3863 cmdlist[aname] = v;
3864 return v;
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; }
3880 ConVarBase v;
3881 string aname = name.idup;
3882 switch (type) {
3883 case "int":
3884 int* var = new int;
3885 v = new ConVar!int(var, aname, null);
3886 break;
3887 case "str":
3888 //string* var = new string; // alas
3889 string[] var;
3890 var.length = 1;
3891 v = new ConVar!string(&var[0], aname, null);
3892 break;
3893 case "bool":
3894 bool* var = new bool;
3895 v = new ConVar!bool(var, aname, null);
3896 break;
3897 case "float":
3898 float* var = new float;
3899 v = new ConVar!float(var, aname, null);
3900 break;
3901 case "double":
3902 double* var = new double;
3903 v = new ConVar!double(var, aname, null);
3904 break;
3905 default:
3906 conwriteln("can't create console variable '", name, "' of unknown type '", type, "'");
3907 return;
3909 v.setAttrs!true(ConVarAttr.User);
3910 addName(aname);
3911 cmdlist[aname] = v;
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");
3930 return;
3932 // remove it
3933 cmdlist.remove(cast(string)name); // it is safe to cast here
3934 foreach (immutable idx, string n; cmdlistSorted) {
3935 if (n == name) {
3936 foreach (immutable c; idx+1..cmdlistSorted.length) cmdlistSorted[c-1] = cmdlistSorted[c];
3937 return;
3940 } else {
3941 conwriteln("console command '", name, "' is not a var");
3942 return;
3944 } else {
3945 conwriteln("console variable '", name, "' doesn't exist");
3946 return;
3952 shared static this () {
3953 addName("echo");
3954 cmdlist["echo"] = new ConCommandEcho();
3955 addName("gc_collect");
3956 cmdlist["gc_collect"] = new ConCommandGCCollect();
3957 addName("alias");
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.
3970 * Params:
3971 * dest = destination buffer
3972 * s = source string
3974 public char[] conFormatStr (char[] dest, ConString s) {
3975 usize dpos = 0;
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;
3981 if (len) {
3982 dest[dpos..dpos+len] = ss[];
3983 dpos += len;
3987 while (s.length) {
3988 usize pos = 0;
3989 while (pos < s.length && s.ptr[pos] != '$') ++pos;
3990 if (s.length-pos > 1 && s.ptr[pos+1] == '$') {
3991 put(s[0..pos+1]);
3992 s = s[pos+2..$];
3993 } else if (s.length-pos <= 1) {
3994 put(s);
3995 break;
3996 } else {
3997 // variable name
3998 ConString vname;
3999 if (pos > 0) put(s[0..pos]);
4000 ++pos;
4001 if (s.ptr[pos] == '{') {
4002 s = s[pos+1..$];
4003 pos = 0;
4004 while (pos < s.length && s.ptr[pos] != '}') ++pos;
4005 vname = s[0..pos];
4006 if (pos < s.length) ++pos;
4007 s = s[pos..$];
4008 } else {
4009 s = s[pos..$];
4010 pos = 0;
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')) {
4014 ++pos;
4015 } else {
4016 break;
4019 vname = s[0..pos];
4020 s = s[pos..$];
4022 if (vname.length) {
4023 if (auto cc = vname in cmdlist) {
4024 if (auto cv = cast(ConVarBase)(*cc)) {
4025 auto v = cv.strval;
4026 put(v);
4027 } else {
4028 put("${!");
4029 put(vname);
4030 put("}");
4032 } else {
4033 put("${");
4034 put(vname);
4035 put("}");
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}!");
4058 char[44] buf;
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 ===");
4080 while (!cl.empty) {
4081 if (cl.frontIsVar) conwrite("VAR ");
4082 else if (cl.frontIsFunc) conwrite("FUNC ");
4083 else conwrite("UNK ");
4084 conwriteln("[", cl.front, "]");
4085 cl.popFront();
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 () {
4098 conRegFunc!(() {
4099 uint maxlen = 0;
4100 foreach (string name; cmdlistSorted[]) {
4101 if (auto ccp = name in cmdlist) {
4102 if (name.length > 64) {
4103 if (maxlen < 64) maxlen = 64;
4104 } else {
4105 if (name.length > maxlen) maxlen = cast(uint)name.length;
4109 foreach (string name; cmdlistSorted[]) {
4110 if (auto ccp = name in cmdlist) {
4111 conwrite(name);
4112 foreach (immutable _; name.length..maxlen) conwrite(" ");
4113 if (auto cmd = cast(ConCommand)(*ccp)) {
4114 conwriteln(" -- ", cmd.help);
4115 } else {
4116 conwriteln(" -- alias");
4120 })("cmdlist", "list all known commands and variables");
4124 // ////////////////////////////////////////////////////////////////////////// //
4125 // simple input buffer for console
4126 private:
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.
4145 * Params:
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);
4151 conclilen = 0;
4152 concurx = 0;
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
4172 if (alloted) {
4173 if (s.length > alloted) {
4174 auto np = cast(char*)realloc(dbuf, s.length);
4175 if (np is null) {
4176 // can't allocate memory
4177 dbuf[0..alloted] = s[0..alloted];
4178 len = alloted;
4179 return;
4181 alloted = cast(uint)s.length;
4183 // it fits!
4184 dbuf[0..s.length] = s[];
4185 len = cast(uint)s.length;
4186 return;
4188 // fit in static buf?
4189 if (s.length <= sbuf.length) {
4190 sbuf.ptr[0..s.length] = s[];
4191 len = cast(uint)s.length;
4192 return;
4194 // allocate dynamic buffer
4195 dbuf = cast(char*)malloc(s.length);
4196 if (dbuf is null) {
4197 // alas; trim and use static one
4198 sbuf[] = s[0..sbuf.length];
4199 len = cast(uint)sbuf.length;
4200 } else {
4201 // ok, use dynamic buffer
4202 alloted = len = cast(uint)s.length;
4203 dbuf[0..len] = s[];
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);
4234 // free used memory
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;
4242 // resize array
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) {
4255 // we need more!
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);
4260 if (np !is null) {
4261 // yay! we did it!
4262 concmdhistory = np;
4263 // clear new items
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];
4275 // move items up
4276 --conhisused;
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;
4303 return -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;
4310 auto orgcmd = cmd;
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);
4315 if (idx >= 0) {
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
4320 // cheatmove! ;-)
4321 ConHistItem tmp = concmdhistory[idx];
4322 // move items up
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);
4326 } else {
4327 // new command
4328 idx = conHistAllot();
4329 if (idx < 0) return; // alas
4330 concmdhistory[idx].str = orgcmd;
4335 /// special characters for `conAddInputChar()`
4336 public enum ConInputChar : char {
4337 Up = '\x01', ///
4338 Down = '\x02', ///
4339 Left = '\x03', ///
4340 Right = '\x04', ///
4341 Home = '\x05', ///
4342 End = '\x06', ///
4343 PageUp = '\x07', ///
4344 Backspace = '\x08', ///
4345 Tab = '\x09', ///
4346 // 0a
4347 PageDown = '\x0b', ///
4348 Delete = '\x0c', ///
4349 Enter = '\x0d', ///
4350 Insert = '\x0e', ///
4352 CtrlY = '\x19', ///
4353 LineUp = '\x1a', ///
4354 LineDown = '\x1b', ///
4355 CtrlW = '\x1c', ///
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;
4369 ++concurx;
4370 } else {
4371 import core.stdc.string : memmove;
4372 memmove(concli.ptr+concurx+1, concli.ptr+concurx, conclilen-concurx);
4373 concli.ptr[concurx++] = ch;
4374 ++conclilen;
4377 return true;
4380 // autocomplete
4381 if (ch == ConInputChar.Tab) {
4382 if (concurx == 0) {
4383 if (++prevWasEmptyAndTab < 2) return;
4384 } else {
4385 prevWasEmptyAndTab = 0;
4387 if (concurx > 0) {
4388 // if there are space(s) before cursor position, this is argument completion
4389 bool doArgAC = false;
4391 int p = concurx;
4392 while (p > 0 && concli.ptr[p-1] > ' ') --p;
4393 doArgAC = (p > 0);
4395 if (doArgAC) {
4396 prevWasEmptyAndTab = 0;
4397 // yeah, arguments; first, get command name
4398 int stp = 0;
4399 while (stp < concurx && concli.ptr[stp] <= ' ') ++stp;
4400 if (stp >= concurx) return; // alas
4401 auto ste = stp+1;
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
4408 return;
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;
4421 usize pos = 0;
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(' ');
4435 } else {
4436 ++concurx;
4437 doRet = true;
4440 conInputIncLastChange();
4441 if (doRet) return;
4445 // nope, print all available commands
4446 bool needDelimiter = true;
4447 foreach (/*auto*/ name; conByCommand) {
4448 if (name.length == 0) continue;
4449 if (concurx > 0) {
4450 if (name.length < concurx) continue;
4451 if (name[0..concurx] != concli[0..concurx]) continue;
4452 } else {
4453 // skip "unusal" commands"
4454 ch = name[0];
4455 if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_')) continue;
4457 if (needDelimiter) { conwriteln("----------------"); needDelimiter = false; }
4458 conwriteln(name);
4460 return;
4462 // process other keys
4463 prevWasEmptyAndTab = 0;
4464 // remove last char
4465 if (ch == ConInputChar.Backspace) {
4466 if (concurx > 0) {
4467 if (concurx < conclilen) {
4468 import core.stdc.string : memmove;
4469 memmove(concli.ptr+concurx-1, concli.ptr+concurx, conclilen-concurx);
4471 --concurx;
4472 --conclilen;
4473 conInputIncLastChange();
4475 return;
4477 // delete char
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);
4484 --conclilen;
4485 conInputIncLastChange();
4487 return;
4489 // ^W (delete previous word)
4490 if (ch == ConInputChar.CtrlW) {
4491 if (concurx > 0) {
4492 int stp = concurx;
4493 // skip spaces
4494 while (stp > 0 && concli.ptr[stp-1] <= ' ') --stp;
4495 // skip non-spaces
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);
4503 conclilen -= dlen;
4504 concurx = stp;
4505 conInputIncLastChange();
4508 return;
4510 // ^Y (delete line)
4511 if (ch == ConInputChar.CtrlY) {
4512 if (conclilen > 0) { conclilen = 0; concurx = 0; conInputIncLastChange(); }
4513 return;
4515 // home
4516 if (ch == ConInputChar.Home) {
4517 if (concurx > 0) {
4518 concurx = 0;
4519 conInputIncLastChange();
4521 return;
4523 // end
4524 if (ch == ConInputChar.End) {
4525 if (concurx < conclilen) {
4526 concurx = conclilen;
4527 conInputIncLastChange();
4529 return;
4531 // up
4532 if (ch == ConInputChar.Up) {
4533 ++conhisidx;
4534 auto cmd = conHistoryAt(conhisidx);
4535 if (cmd.length == 0) {
4536 --conhisidx;
4537 } else {
4538 concli[0..cmd.length] = cmd[];
4539 conclilen = cast(uint)cmd.length;
4540 concurx = conclilen;
4541 conInputIncLastChange();
4543 return;
4545 // down
4546 if (ch == ConInputChar.Down) {
4547 --conhisidx;
4548 auto cmd = conHistoryAt(conhisidx);
4549 if (cmd.length == 0 && conhisidx < -1) {
4550 ++conhisidx;
4551 } else {
4552 concli[0..cmd.length] = cmd[];
4553 conclilen = cast(uint)cmd.length;
4554 concurx = conclilen;
4555 conInputIncLastChange();
4557 return;
4559 // left
4560 if (ch == ConInputChar.Left) {
4561 if (concurx > 0) {
4562 --concurx;
4563 conInputIncLastChange();
4565 return;
4567 // right
4568 if (ch == ConInputChar.Right) {
4569 if (concurx < conclilen) {
4570 ++concurx;
4571 conInputIncLastChange();
4573 return;
4575 // other
4576 if (ch < ' ' || ch > 127) return;
4577 if (insChars(ch)) conInputIncLastChange();
4581 // ////////////////////////////////////////////////////////////////////////// //
4582 private:
4584 /// add console command to execution queue (thread-safe)
4585 public void concmd (ConString cmd) {
4586 consoleLock();
4587 scope(exit) { consoleUnlock(); conInputIncLastChange(); }
4588 concmdAdd(cmd);
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) {
4606 consoleLock();
4607 scope(exit) { consoleUnlock(); conInputIncLastChange(); }
4609 auto fmtanchor = fmt;
4610 usize pos = 0;
4611 bool ensureCmd = true;
4612 char inQ = 0;
4614 usize stpos = usize.max;
4616 void puts(bool quote=false) (const(char)[] s...) {
4617 if (s.length) {
4618 if (ensureCmd) { concmdEnsureNewCommand(); ensureCmd = false; }
4619 if (stpos == usize.max) stpos = concmdbufpos;
4620 static if (quote) {
4621 char[8] buf;
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]);
4627 } else {
4628 concmdAdd!false(s[idx..idx+1]);
4631 } else {
4632 concmdAdd!false(s);
4637 void putint(TT) (TT nn) {
4638 char[32] buf = ' ';
4640 static if (is(TT == shared)) {
4641 import core.atomic;
4642 alias T = XUQQ!TT;
4643 T n = atomicLoad(nn);
4644 } else {
4645 alias T = XUQQ!TT;
4646 T n = 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)) {
4660 enum neg = false;
4661 } else {
4662 bool neg = (n < 0);
4663 if (neg) n = -(n);
4666 int bpos = buf.length;
4667 do {
4668 //if (bpos == 0) assert(0, "internal printer error");
4669 buf.ptr[--bpos] = cast(char)('0'+n%10);
4670 n /= 10;
4671 } while (n != 0);
4672 if (neg) {
4673 //if (bpos == 0) assert(0, "internal printer error");
4674 buf.ptr[--bpos] = '-';
4676 puts(buf[bpos..$]);
4679 void putfloat(TT) (TT nn) {
4680 import core.stdc.stdlib : malloc, realloc;
4682 static if (is(TT == shared)) {
4683 import core.atomic;
4684 alias T = XUQQ!TT;
4685 T n = atomicLoad(nn);
4686 } else {
4687 alias T = XUQQ!TT;
4688 T n = nn;
4691 static char* buf;
4692 static usize buflen = 0;
4694 if (buf is null) {
4695 buflen = 256;
4696 buf = cast(char*)malloc(buflen);
4697 if (buf is null) assert(0, "out of memory");
4700 for (;;) {
4701 import core.stdc.stdio : snprintf;
4702 auto plen = snprintf(buf, buflen, "%g", cast(double)n);
4703 if (plen >= buflen) {
4704 buflen = plen+2;
4705 buf = cast(char*)realloc(buf, buflen);
4706 if (buf is null) assert(0, "out of memory");
4707 } else {
4708 puts(buf[0..plen]);
4709 return;
4714 static bool isGoodIdChar (char ch) pure nothrow @safe @nogc {
4715 pragma(inline, true);
4716 return
4717 (ch >= '0' && ch <= '9') ||
4718 (ch >= 'A' && ch <= 'Z') ||
4719 (ch >= 'a' && ch <= 'z') ||
4720 ch == '_';
4723 void processUntilFSp () {
4724 while (pos < fmt.length) {
4725 usize epos = pos;
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]);
4729 puts(fmt[epos+1]);
4730 pos = (epos += 2);
4731 continue;
4733 if (inQ && fmt[epos] == inQ) inQ = 0;
4734 if (fmt[epos] == '"') {
4735 inQ = '"';
4736 } else if (fmt[epos] == '$') {
4737 puts(fmt[pos..epos]);
4738 pos = epos;
4739 if (fmt.length-epos > 0 && fmt[epos+1] != '{') {
4740 // simple
4741 ++epos;
4742 while (epos < fmt.length && isGoodIdChar(fmt[epos])) ++epos;
4743 if (epos-pos > 1) {
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);
4747 pos = epos;
4748 continue;
4752 // go on
4753 } else {
4754 // complex
4755 epos += 2;
4756 //FIXME: allow escaping of '}'
4757 while (epos < fmt.length && fmt[epos] != '}') ++epos;
4758 if (epos >= fmt.length) { epos = pos+1; continue; } // no '}'
4759 pos += 2;
4760 usize cpos = pos;
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]);
4768 } else {
4769 if (inQ) puts!true(sv); else puts!false(sv);
4771 pos = epos+1;
4772 continue;
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]);
4779 pos = epos+1;
4780 continue;
4783 ++epos;
4785 if (epos > pos) {
4786 puts(fmt[pos..epos]);
4787 pos = epos;
4788 if (pos >= fmt.length) break;
4790 if (fmt.length-pos < 2 || fmt[pos+1] == '%') { puts('%'); pos += 2; continue; }
4791 break;
4795 foreach (immutable argnum, /*auto*/ att; A) {
4796 processUntilFSp();
4797 if (pos >= fmt.length) assert(0, "out of format specifiers for arguments");
4798 assert(fmt[pos] == '%');
4799 ++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++]) {
4804 case 'q':
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)) {
4816 puts(mname);
4817 dumpNum = false;
4818 break;
4821 //FIXME: check sign
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");
4829 } else {
4830 import std.conv : to;
4831 puts!true(to!string(args[argnum]));
4833 break;
4834 case 's':
4835 case 'd': // oops
4836 case 'u': // oops
4837 if (inQ) goto case 'q';
4838 static if (is(at == char)) {
4839 puts(args[argnum]);
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)) {
4849 puts(mname);
4850 dumpNum = false;
4851 break;
4854 //FIXME: check sign
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)[])) {
4859 puts(args[argnum]);
4860 } else static if (is(at : T*, T)) {
4861 assert(0, "can't put pointers");
4862 } else {
4863 import std.conv : to;
4864 puts(to!string(args[argnum]));
4866 break;
4867 default:
4868 assert(0, "invalid format specifier");
4872 while (pos < fmt.length) {
4873 processUntilFSp();
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]);
4882 } else {
4883 cmddg("");
4889 /// get console variable value; doesn't do complex conversions! (thread-safe)
4890 public T convar(T) (ConString s) {
4891 consoleLock();
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) {
4899 consoleLock();
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;
4921 optr = arr.ptr;
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);
4935 // make room
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) {
4956 if (s.length) {
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.
4973 * Returns:
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.
4986 * WARNING:
4988 * Params:
4989 * maxlen = maximum queue size to process (0: don't process commands added while processing ;-)
4991 * Returns:
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
5000 consoleLock();
5001 scope(exit) consoleUnlock();
5002 if (concmdbufpos == 0) return false;
5004 bool once = (maxlen == 0);
5005 if (once) maxlen = uint.max;
5006 for (;;) {
5007 ConString s;
5009 consoleLock();
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;
5021 import core.thread;
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]; }
5031 if (!once) {
5032 if (maxlen <= ebuf) maxlen = 0; else maxlen -= ebuf;
5034 concmdbufpos = 0;
5035 if (s.length == 0) break;
5037 // process commands
5038 while (s.length) {
5039 auto cmd = conGetCommandStr(s);
5040 if (cmd is null) break;
5041 if (cmd.length > int.max/8) { conwriteln("command too long"); continue; }
5042 try {
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, ">");
5047 consoleLock();
5048 scope(exit) consoleUnlock();
5049 if (ccWaitPrependStrLen) { concmdPrepend(prependStr); clearPrependStr(); }
5050 if (s.length) { setPrependStr(s); concmdPrepend(prependStr); clearPrependStr(); }
5051 once = true;
5052 break;
5054 version(iv_cmdcon_debug_wait) conwriteln(" ++++++++++");
5055 } catch (Exception e) {
5056 conwriteln("***ERROR: ", e.msg);
5059 if (once || maxlen == 0) break;
5062 consoleLock();
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
5078 * Params:
5079 * immediate = immediately call `conProcessQueue()` with some big limit
5080 * args = those arg vector passed to `main()`
5082 * Returns:
5083 * `true` is any command was added to queue.
5085 public bool conProcessArgs(bool immediate=false) (ref string[] args) {
5086 //consoleLock();
5087 //scope(exit) consoleUnlock();
5089 bool ensureCmd = true;
5090 auto ocbpos = concmdbufpos;
5092 void puts (const(char)[] s...) {
5093 if (s.length) {
5094 if (ensureCmd) {
5095 concmdEnsureNewCommand();
5096 ensureCmd = false;
5097 } else {
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; }
5103 if (doQuote) {
5104 concmdAdd!false("\"");
5105 foreach (immutable idx, char ch; s) {
5106 if (ch < ' ' || ch == 127 || ch == '"' || ch == '\\') {
5107 import core.stdc.stdio : snprintf;
5108 char[8] buf = 0;
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]);
5112 } else {
5113 concmdAdd!false(s[idx..idx+1]);
5116 concmdAdd!false("\"");
5117 } else {
5118 concmdAdd!false(s);
5123 static if (immediate) bool res = false;
5125 usize idx = 1;
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
5132 --idx;
5133 foreach (immutable c; idx+1..args.length) args[c-1] = args[c];
5134 args.length -= 1;
5135 continue;
5137 if (a[0] == '+') {
5138 scope(exit) ensureCmd = true;
5139 auto xidx = idx-1;
5140 puts(a[1..$]);
5141 while (idx < args.length) {
5142 a = args[idx];
5143 if (a.length > 0) {
5144 if (a[0] == '+') break;
5145 puts(a);
5147 ++idx;
5149 foreach (immutable c; idx..args.length) args[xidx+c-idx] = args[c];
5150 args.length -= idx-xidx;
5151 idx = xidx;
5152 // execute it immediately if we're asked to do so
5153 static if (immediate) {
5154 if (concmdbufpos > ocbpos) {
5155 res = true;
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) {
5172 char[16] buf;
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 // ////////////////////////////////////////////////////////////////////////// //
5185 // "exec" command
5186 static if (__traits(compiles, (){import iv.vfs;}())) {
5187 enum CmdConHasVFS = true;
5188 import iv.vfs;
5189 } else {
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;
5202 ++stpos;
5204 return -1;
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) {
5214 try {
5215 string newfname = null;
5216 if (conExecPathCheck !is null) {
5217 newfname = conExecPathCheck(fname);
5218 if (newfname !is null && newfname.length == 0) {
5219 // oops
5220 throw new Exception("invalid script path");
5223 static if (CmdConHasVFS) {
5224 auto fl = VFile(newfname is null ? fname : newfname);
5225 } else {
5226 auto fl = File(newfname is null ? fname.idup : newfname);
5228 auto sz = fl.size;
5229 if (sz > 1024*1024*64) throw new Exception("script file too big");
5230 if (sz > 0) {
5231 enum AbortCmd = "!!abort!!";
5232 auto s = new char[](cast(uint)sz);
5233 auto anchor = s;
5234 scope(exit) delete anchor;
5235 static if (CmdConHasVFS) {
5236 fl.rawReadExact(s);
5237 } else {
5238 auto tbuf = s;
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);
5247 while (apos >= 0) {
5248 if (s.length-apos <= AbortCmd.length || s[apos+AbortCmd.length] <= ' ') {
5249 bool good = true;
5250 // it should be the first command, not commented
5251 auto pos = apos;
5252 while (pos > 0 && s[pos-1] != '\n') {
5253 if (s[pos-1] != ' ' && s[pos-1] != '\t') { good = false; break; }
5254 --pos;
5256 if (good) { s = s[0..apos]; break; }
5258 // check next
5259 apos = s.xindexof(AbortCmd, apos+1);
5262 concmd(s);
5264 } catch (Exception e) {
5265 if (!silent) conwriteln("ERROR loading script \"", fname, "\"");
5267 })("exec", "execute console script (name [silent_failure_flag])");