iv.egra: low-level gfx and high-level GUI library
[iv.d.git] / cmdcon / core.d
blob08ff829c6b4c5996000646e6735ca24719f3aff1
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;
38 //version = iv_cmdcon_debug_wait;
39 //version = iv_cmdcon_honest_ctfe_writer;
42 // ////////////////////////////////////////////////////////////////////////// //
43 /// use this in conGetVar, for example, to avoid allocations
44 public alias ConString = const(char)[];
47 // ////////////////////////////////////////////////////////////////////////// //
48 public enum ConDump : int { none, stdout, stderr }
49 shared ConDump conStdoutFlag = ConDump.stdout;
50 public @property ConDump conDump () nothrow @trusted @nogc { pragma(inline, true); import core.atomic : atomicLoad; return atomicLoad(conStdoutFlag); } /// write console output to ...
51 public @property void conDump (ConDump v) nothrow @trusted @nogc { pragma(inline, true); import core.atomic : atomicStore; atomicStore(conStdoutFlag, v); } /// ditto
53 shared static this () {
54 conRegVar!conStdoutFlag("console_dump", "dump console output (none, stdout, stderr)");
58 // ////////////////////////////////////////////////////////////////////////// //
59 import core.sync.mutex : Mutex;
60 __gshared Mutex consoleLocker;
61 __gshared Mutex consoleWriteLocker;
62 shared static this () {
63 consoleLocker = new Mutex();
64 consoleWriteLocker = new Mutex();
68 // ////////////////////////////////////////////////////////////////////////// //
69 enum isGShared(alias v) = !__traits(compiles, ()@safe{auto _=&v;}());
70 enum isShared(alias v) = is(typeof(v) == shared);
71 enum isGoodVar(alias v) = isGShared!v || isShared!v;
72 //alias isGoodVar = isGShared;
75 // ////////////////////////////////////////////////////////////////////////// //
76 // ascii only
77 private bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
78 if (s0.length != s1.length) return false;
79 foreach (immutable idx, char c0; s0) {
80 // try the easiest case first
81 if (__ctfe) {
82 if (c0 == s1[idx]) continue;
83 } else {
84 if (c0 == s1.ptr[idx]) continue;
86 c0 |= 0x20; // convert to ascii lowercase
87 if (c0 < 'a' || c0 > 'z') return false; // it wasn't a letter, no need to check the second char
88 // c0 is guaranteed to be a lowercase ascii here
89 if (__ctfe) {
90 if (c0 != (s1[idx]|0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
91 } else {
92 if (c0 != (s1.ptr[idx]|0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
95 return true;
99 // ////////////////////////////////////////////////////////////////////////// //
101 version(test_cbuf)
102 enum ConBufSize = 64;
103 else
104 enum ConBufSize = 256*1024;
107 // each line in buffer ends with '\n'; we don't keep offsets or lengthes, as
108 // it's fairly easy to search in buffer, and drawing console is not a common
109 // thing, so it doesn't have to be superfast.
110 __gshared char* cbuf = null;
111 __gshared int cbufhead, cbuftail; // `cbuftail` points *at* last char
112 __gshared bool cbufLastWasCR = false;
113 __gshared uint cbufcursize = 0;
114 __gshared uint cbufmaxsize = 256*1024;
116 shared static this () {
117 import core.stdc.stdlib : malloc;
118 //cbuf.ptr[0] = '\n';
119 version(test_cbuf) {
120 cbufcursize = 32;
121 cbufmaxsize = 256;
122 } else {
123 cbufcursize = 8192;
125 cbuf = cast(char*)malloc(cbufcursize);
126 if (cbuf is null) assert(0, "cmdcon: out of memory!"); // unlikely case
127 cbuf[0..cbufcursize] = 0;
128 cbuf[0] = '\n';
130 conRegVar("con_bufsize", "current size of console output buffer", (ConVarBase self) => cbufcursize, (ConVarBase self, uint nv) {}, ConVarAttr.ReadOnly);
131 conRegVar!cbufmaxsize(8192, 512*1024*1024, "con_bufmaxsize", "maximum size of console output buffer");
134 shared uint changeCount = 1;
135 public @property uint cbufLastChange () nothrow @trusted @nogc { import core.atomic; return atomicLoad(changeCount); } /// changed when something was written to console buffer
138 // ////////////////////////////////////////////////////////////////////////// //
139 version(aliced) {} else {
140 private auto assumeNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ {
141 import std.traits;
142 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_;
143 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
148 /// multithread lock
149 public void consoleLock() () nothrow @trusted @nogc {
150 version(aliced) pragma(inline, true);
151 version(aliced) {
152 consoleLocker.lock();
153 } else {
154 //try { consoleLocker.lock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
155 assumeNoThrowNoGC(() { consoleLocker.lock(); })();
159 /// multithread unlock
160 public void consoleUnlock() () nothrow @trusted @nogc {
161 version(aliced) pragma(inline, true);
162 version(aliced) {
163 consoleLocker.unlock();
164 } else {
165 //try { consoleLocker.unlock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
166 assumeNoThrowNoGC(() { consoleLocker.unlock(); })();
170 // multithread lock
171 public void consoleWriteLock() () nothrow @trusted @nogc {
172 version(aliced) pragma(inline, true);
173 version(aliced) {
174 consoleWriteLocker.lock();
175 } else {
176 //try { consoleWriteLocker.lock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
177 assumeNoThrowNoGC(() { consoleWriteLocker.lock(); })();
181 // multithread unlock
182 public void consoleWriteUnlock() () nothrow @trusted @nogc {
183 version(aliced) pragma(inline, true);
184 version(aliced) {
185 consoleWriteLocker.unlock();
186 } else {
187 //try { consoleWriteLocker.unlock(); } catch (Exception e) { assert(0, "fuck vanilla!"); }
188 assumeNoThrowNoGC(() { consoleWriteLocker.unlock(); })();
192 public __gshared void delegate () nothrow @trusted @nogc conWasNewLineCB; /// can be called in any thread; called if some '\n' was output with `conwrite*()`
195 // ////////////////////////////////////////////////////////////////////////// //
196 /// put characters to console buffer (and, possibly, STDOUT_FILENO). thread-safe.
197 public void cbufPut() (scope ConString chrs...) nothrow @trusted /*@nogc*/ { pragma(inline, true); if (chrs.length) cbufPutIntr!true(chrs); }
199 public void cbufPutIntr(bool dolock) (scope ConString chrs...) nothrow @trusted /*@nogc*/ {
200 if (chrs.length) {
201 import core.atomic : atomicLoad, atomicOp;
202 static if (dolock) consoleWriteLock();
203 static if (dolock) scope(exit) consoleWriteUnlock();
204 final switch (/*atomicLoad(conStdoutFlag)*/cast(ConDump)conStdoutFlag) {
205 case ConDump.none: break;
206 case ConDump.stdout:
207 version(Windows) {
208 //fuck
209 import core.sys.windows.winbase;
210 import core.sys.windows.windef;
211 import core.sys.windows.wincon;
212 auto hh = GetStdHandle(STD_OUTPUT_HANDLE);
213 if (hh != INVALID_HANDLE_VALUE) {
214 DWORD ww;
215 usize xpos = 0;
216 while (xpos < chrs.length) {
217 usize epos = xpos;
218 while (epos < chrs.length && chrs.ptr[epos] != '\n') ++epos;
219 if (epos > xpos) WriteConsoleA(hh, chrs.ptr, cast(DWORD)chrs.length, &ww, null);
220 if (epos < chrs.length) WriteConsoleA(hh, "\r\n".ptr, cast(DWORD)2, &ww, null);
221 xpos = epos+1;
224 } else {
225 import core.sys.posix.unistd : STDOUT_FILENO, write;
226 write(STDOUT_FILENO, chrs.ptr, chrs.length);
228 break;
229 case ConDump.stderr:
230 version(Windows) {
231 //fuck
232 import core.sys.windows.winbase;
233 import core.sys.windows.windef;
234 import core.sys.windows.wincon;
235 auto hh = GetStdHandle(STD_ERROR_HANDLE);
236 if (hh != INVALID_HANDLE_VALUE) {
237 DWORD ww;
238 usize xpos = 0;
239 while (xpos < chrs.length) {
240 usize epos = xpos;
241 while (epos < chrs.length && chrs.ptr[epos] != '\n') ++epos;
242 if (epos > xpos) WriteConsoleA(hh, chrs.ptr, cast(DWORD)chrs.length, &ww, null);
243 if (epos < chrs.length) WriteConsoleA(hh, "\r\n".ptr, cast(DWORD)2, &ww, null);
244 xpos = epos+1;
247 } else {
248 import core.sys.posix.unistd : STDERR_FILENO, write;
249 write(STDERR_FILENO, chrs.ptr, chrs.length);
251 break;
253 //atomicOp!"+="(changeCount, 1);
254 bool wasNewLine = false;
255 (*cast(uint*)&changeCount)++;
256 foreach (char ch; chrs) {
257 if (cbufLastWasCR && ch == '\x0a') { cbufLastWasCR = false; continue; }
258 if ((cbufLastWasCR = (ch == '\x0d')) != false) ch = '\x0a';
259 wasNewLine = wasNewLine || (ch == '\x0a');
260 int np = (cbuftail+1)%cbufcursize;
261 if (np == cbufhead) {
262 // we have to make some room; delete top line for this
263 bool removelines = true;
264 // first, try grow cosole buffer if we can
265 if (cbufcursize < cbufmaxsize) {
266 import core.stdc.stdlib : realloc;
267 version(test_cbuf) {
268 auto newsz = cbufcursize+13;
269 } else {
270 auto newsz = cbufcursize*2;
272 if (newsz > cbufmaxsize) newsz = cbufmaxsize;
273 //{ import core.stdc.stdio : stderr, fprintf; stderr.fprintf("*** CON: %u -> %u; head=%u; tail=%u; np=%u\n", cbufcursize, newsz, cbufhead, cbuftail, np); }
274 auto nbuf = cast(char*)realloc(cbuf, newsz);
275 if (nbuf !is null) {
276 // yay, we got some room; move text down, so there will be more room
277 version(test_cbuf) { import core.stdc.stdio : stderr, fprintf; stderr.fprintf("np=%u; cbufcursize=%u; cbufmaxsize=%u; newsz=%u; ndest=%u\n", np, cbufcursize, cbufmaxsize, newsz, newsz-cbufcursize, cbuf+np); }
278 if (np == 0) {
279 // yay, we can do some trickery here
280 np = cbuftail+1; // yep, that easy
281 } else {
282 import core.stdc.string : memmove;
283 auto tsz = cbufcursize-np;
284 if (tsz > 0) memmove(cbuf+newsz-tsz, cbuf+np, tsz);
285 cbufhead += newsz-cbufcursize;
287 cbufcursize = newsz;
288 cbuf = nbuf;
289 removelines = false;
290 version(test_cbuf) { import core.stdc.stdio : stderr, fprintf; stderr.fprintf(" cbufhead=%u; cbuftail=%u\n", cbufhead, cbuftail); }
291 assert(np < cbufcursize);
294 if (removelines) {
295 for (;;) {
296 char och = cbuf[cbufhead];
297 cbufhead = (cbufhead+1)%cbufcursize;
298 if (cbufhead == np || och == '\n') break;
302 cbuf[np] = ch;
303 cbuftail = np;
305 if (wasNewLine && conWasNewLineCB !is null) conWasNewLineCB();
310 // ////////////////////////////////////////////////////////////////////////// //
311 /// range of conbuffer lines, from last. not thread-safe.
312 /// warning! don't modify conbuf while the range is active!
313 public auto conbufLinesRev () nothrow @trusted @nogc {
314 static struct Line {
315 nothrow @trusted @nogc:
316 private:
317 int h, t; // head and tail, to check validity
318 int sp = -1, ep;
320 public:
321 @property auto front () const { pragma(inline, true); return (sp >= 0 ? cbuf[sp] : '\x00'); }
322 @property bool empty () const { pragma(inline, true); return (sp < 0 || h != cbufhead || t != cbuftail); }
323 @property auto save () { pragma(inline, true); return Line(h, t, sp, ep); }
324 void popFront () { pragma(inline, true); if (sp < 0 || (sp = (sp+1)%cbufcursize) == ep) sp = -1; }
325 @property usize opDollar () { pragma(inline, true); return (sp >= 0 ? (sp > ep ? ep+cbufcursize-sp : ep-sp) : 0); }
326 alias length = opDollar;
327 char opIndex (usize pos) { pragma(inline, true); return (sp >= 0 ? cbuf[sp+pos] : '\x00'); }
330 static struct Range {
331 nothrow @trusted @nogc:
332 private:
333 int h, t; // head and tail, to check validity
334 int pos; // position of prev line
335 Line line;
337 void toLineStart () {
338 line.ep = pos;
339 while (pos != cbufhead) {
340 int p = (pos+cbufcursize-1)%cbufcursize;
341 if (cbuf[p] == '\n') break;
342 pos = p;
344 line.sp = pos;
345 line.h = h;
346 line.t = t;
349 public:
350 @property auto front () pure { pragma(inline, true); return line; }
351 @property bool empty () const { pragma(inline, true); return (pos < 0 || pos == h || h != cbufhead || t != cbuftail); }
352 @property auto save () { pragma(inline, true); return Range(h, t, pos, line); }
353 void popFront () {
354 if (pos < 0 || pos == h || h != cbufhead || t != cbuftail) { line = Line.init; h = t = pos = -1; return; }
355 pos = (pos+cbufcursize-1)%cbufcursize;
356 toLineStart();
360 Range res;
361 res.h = cbufhead;
362 res.pos = res.t = cbuftail;
363 if (cbuf[res.pos] != '\n') res.pos = (res.pos+1)%cbufcursize;
364 res.toLineStart();
365 //{ import std.stdio; writeln("pos=", res.pos, "; head=", res.h, "; tail=", res.t, "; llen=", res.line.length, "; [", res.line, "]"); }
366 return res;
370 // ////////////////////////////////////////////////////////////////////////// //
371 version(unittest) public void conbufDump () {
372 import std.stdio;
373 int pp = cbufhead;
374 stdout.writeln("==========================");
375 for (;;) {
376 if (cbuf[pp] == '\n') stdout.write('|');
377 stdout.write(cbuf[pp]);
378 if (pp == cbuftail) {
379 if (cbuf[pp] != '\n') stdout.write('\n');
380 break;
382 pp = (pp+1)%cbufcursize;
384 //foreach (/+auto+/ s; conbufLinesRev) stdout.writeln(s, "|");
388 // ////////////////////////////////////////////////////////////////////////// //
389 version(test_cbuf) unittest {
390 conbufDump();
391 cbufPut("boo\n"); conbufDump();
392 cbufPut("this is another line\n"); conbufDump();
393 cbufPut("one more line\n"); conbufDump();
394 cbufPut("foo\n"); conbufDump();
395 cbufPut("more lines!\n"); conbufDump();
396 cbufPut("and even more lines!\n"); conbufDump();
397 foreach (immutable idx; 0..256) {
398 import std.string : format;
399 cbufPut("line %s\n".format(idx));
400 conbufDump();
405 // ////////////////////////////////////////////////////////////////////////// //
406 // writer
407 private import std.traits/* : isBoolean, isIntegral, isPointer*/;
408 //private alias StripTypedef(T) = T;
410 //__gshared void delegate (scope const(char[])) @trusted nothrow @nogc conwriter;
411 //public @property auto ConWriter () @trusted nothrow @nogc { return conwriter; }
412 //public @property auto ConWriter (typeof(conwriter) cv) @trusted nothrow @nogc { auto res = conwriter; conwriter = cv; return res; }
414 void conwriter() (scope ConString str) nothrow @trusted /*@nogc*/ {
415 pragma(inline, true);
416 if (str.length > 0) cbufPut(str);
420 // ////////////////////////////////////////////////////////////////////////// //
421 // i am VERY sorry for this!
422 private template XUQQ(T) {
423 static if (is(T : shared TT, TT)) {
424 static if (is(TT : const TX, TX)) alias XUQQ = TX;
425 else static if (is(TT : immutable TX, TX)) alias XUQQ = TX;
426 else alias XUQQ = TT;
427 } else static if (is(T : const TT, TT)) {
428 static if (is(TT : shared TX, TX)) alias XUQQ = TX;
429 else static if (is(TT : immutable TX, TX)) alias XUQQ = TX;
430 else alias XUQQ = TT;
431 } else static if (is(T : immutable TT, TT)) {
432 static if (is(TT : const TX, TX)) alias XUQQ = TX;
433 else static if (is(TT : shared TX, TX)) alias XUQQ = TX;
434 else alias XUQQ = TT;
435 } else {
436 alias XUQQ = T;
439 static assert(is(XUQQ!string == string));
440 static assert(is(XUQQ!(const(char)[]) == const(char)[]));
443 // ////////////////////////////////////////////////////////////////////////// //
444 private void cwrxputch (scope const(char)[] s...) nothrow @trusted @nogc {
445 pragma(inline, true);
446 if (s.length) cbufPutIntr!false(s); // no locking
450 private void cwrxputstr(bool cutsign=false) (scope const(char)[] str, char signw, char lchar, char rchar, int wdt, int maxwdt) nothrow @trusted @nogc {
451 if (maxwdt < 0) {
452 if (maxwdt == maxwdt.min) ++maxwdt; // alas
453 maxwdt = -(maxwdt);
454 if (str.length > maxwdt) str = str[$-maxwdt..$];
455 } else if (maxwdt > 0) {
456 if (str.length > maxwdt) str = str[0..maxwdt];
458 static if (cutsign) {
459 if (signw == ' ' && wdt && str.length && str.length < wdt) {
460 if (str.ptr[0] == '-' || str.ptr[0] == '+') {
461 cwrxputch(str.ptr[0]);
462 str = str[1..$];
463 --wdt;
467 if (str.length < wdt) {
468 // '+' means "center"
469 if (signw == '+') {
470 foreach (immutable _; 0..(wdt-str.length)/2) cwrxputch(lchar);
471 } else if (signw != '-') {
472 foreach (immutable _; 0..wdt-str.length) cwrxputch(lchar);
475 cwrxputch(str);
476 if (str.length < wdt) {
477 // '+' means "center"
478 if (signw == '+') {
479 foreach (immutable _; ((wdt-str.length)/2)+str.length..wdt) cwrxputch(rchar);
480 } else if (signw == '-') {
481 foreach (immutable _; 0..wdt-str.length) cwrxputch(rchar);
487 private void cwrxputstrz(bool cutsign=false) (scope const(char)* str, char signw, char lchar, char rchar, int wdt, int maxwdt) nothrow @trusted @nogc {
488 if (str is null) {
489 cwrxputstr!cutsign("", signw, lchar, rchar, wdt, maxwdt);
490 } else {
491 auto end = str;
492 while (*end) ++end;
493 cwrxputstr!cutsign(str[0..cast(usize)(end-str)], signw, lchar, rchar, wdt, maxwdt);
498 private void cwrxputchar (char ch, char signw, char lchar, char rchar, int wdt, int maxwdt) nothrow @trusted @nogc {
499 cwrxputstr((&ch)[0..1], signw, lchar, rchar, wdt, maxwdt);
503 private void cwrxputint(TT) (TT nn, char signw, char lchar, char rchar, int wdt, int maxwdt) nothrow @trusted @nogc {
504 char[32] buf = ' ';
506 static if (is(TT == shared)) {
507 import core.atomic;
508 alias T = XUQQ!TT;
509 T n = atomicLoad(nn);
510 } else {
511 alias T = XUQQ!TT;
512 T n = nn;
515 static if (is(T == long)) {
516 if (n == 0x8000_0000_0000_0000uL) { cwrxputstr!true("-9223372036854775808", signw, lchar, rchar, wdt, maxwdt); return; }
517 } else static if (is(T == int)) {
518 if (n == 0x8000_0000u) { cwrxputstr!true("-2147483648", signw, lchar, rchar, wdt, maxwdt); return; }
519 } else static if (is(T == short)) {
520 if ((n&0xffff) == 0x8000u) { cwrxputstr!true("-32768", signw, lchar, rchar, wdt, maxwdt); return; }
521 } else static if (is(T == byte)) {
522 if ((n&0xff) == 0x80u) { cwrxputstr!true("-128", signw, lchar, rchar, wdt, maxwdt); return; }
525 static if (__traits(isUnsigned, T)) {
526 enum neg = false;
527 } else {
528 bool neg = (n < 0);
529 if (neg) n = -(n);
532 int bpos = buf.length;
533 do {
534 //if (bpos == 0) assert(0, "internal printer error");
535 buf.ptr[--bpos] = cast(char)('0'+n%10);
536 n /= 10;
537 } while (n != 0);
538 if (neg) {
539 //if (bpos == 0) assert(0, "internal printer error");
540 buf.ptr[--bpos] = '-';
542 cwrxputstr!true(buf[bpos..$], signw, lchar, rchar, wdt, maxwdt);
546 private void cwrxputhex(TT) (TT nn, bool upcase, char signw, char lchar, char rchar, int wdt, int maxwdt) nothrow @trusted @nogc {
547 char[32] buf = ' ';
549 static if (is(TT == shared)) {
550 import core.atomic;
551 alias T = XUQQ!TT;
552 T n = atomicLoad(nn);
553 } else {
554 alias T = XUQQ!TT;
555 T n = nn;
558 static if (is(T == long)) {
559 if (n == 0x8000_0000_0000_0000uL) { cwrxputstr!true("-8000000000000000", signw, lchar, rchar, wdt, maxwdt); return; }
560 } else static if (is(T == int)) {
561 if (n == 0x8000_0000u) { cwrxputstr!true("-80000000", signw, lchar, rchar, wdt, maxwdt); return; }
562 } else static if (is(T == short)) {
563 if (n == 0x8000u) { cwrxputstr!true("-8000", signw, lchar, rchar, wdt, maxwdt); return; }
564 } else static if (is(T == byte)) {
565 if (n == 0x80u) { cwrxputstr!true("-80", signw, lchar, rchar, wdt, maxwdt); return; }
568 static if (__traits(isUnsigned, T)) {
569 enum neg = false;
570 } else {
571 bool neg = (n < 0);
572 if (neg) n = -(n);
575 int bpos = buf.length;
576 do {
577 //if (bpos == 0) assert(0, "internal printer error");
578 immutable ubyte b = n&0x0f;
579 n >>= 4;
580 if (b < 10) buf.ptr[--bpos] = cast(char)('0'+b); else buf.ptr[--bpos] = cast(char)((upcase ? 'A' : 'a')+(b-10));
581 } while (n != 0);
582 if (neg) {
583 //if (bpos == 0) assert(0, "internal printer error");
584 buf.ptr[--bpos] = '-';
586 cwrxputstr!true(buf[bpos..$], signw, lchar, rchar, wdt, maxwdt);
590 private void cwrxputfloat(TT) (TT nn, bool simple, char signw, char lchar, char rchar, int wdt, int maxwdt) nothrow @trusted @nogc {
591 import core.stdc.stdlib : malloc, realloc;
593 static if (is(TT == shared)) {
594 import core.atomic;
595 alias T = XUQQ!TT;
596 T n = atomicLoad(nn);
597 } else {
598 alias T = XUQQ!TT;
599 T n = nn;
602 static char* buf;
603 static usize buflen = 0;
604 char[256] fmtstr;
605 int fspos = 0;
607 if (buf is null) {
608 buflen = 256;
609 buf = cast(char*)malloc(buflen);
610 if (buf is null) assert(0, "out of memory");
613 void putNum (int n) {
614 if (n == n.min) assert(0, "oops");
615 if (n < 0) { fmtstr.ptr[fspos++] = '-'; n = -(n); }
616 char[24] buf = void;
617 int bpos = buf.length;
618 do {
619 buf.ptr[--bpos] = cast(char)('0'+n%10);
620 n /= 10;
621 } while (n != 0);
622 if (fmtstr.length-fspos < buf.length-bpos) assert(0, "internal printer error");
623 fmtstr.ptr[fspos..fspos+(buf.length-bpos)] = buf[bpos..$];
624 fspos += buf.length-bpos;
627 fmtstr.ptr[fspos++] = '%';
628 if (!simple) {
629 if (wdt) {
630 if (signw == '-') fmtstr.ptr[fspos++] = '-';
631 putNum(wdt);
633 if (maxwdt) {
634 fmtstr.ptr[fspos++] = '.';
635 putNum(maxwdt);
637 fmtstr.ptr[fspos++] = 'g';
638 maxwdt = 0;
639 } else {
640 fmtstr.ptr[fspos++] = 'g';
642 fmtstr.ptr[fspos++] = '\x00';
643 //{ import core.stdc.stdio; printf("<%s>", fmtstr.ptr); }
645 for (;;) {
646 import core.stdc.stdio : snprintf;
647 auto plen = snprintf(buf, buflen, fmtstr.ptr, cast(double)n);
648 if (plen >= buflen) {
649 buflen = plen+2;
650 buf = cast(char*)realloc(buf, buflen);
651 if (buf is null) assert(0, "out of memory");
652 } else {
653 if (lchar != ' ') {
654 foreach (ref ch; buf[0..plen]) {
655 if (ch != ' ') break;
656 ch = lchar;
659 if (rchar != ' ') {
660 foreach_reverse (ref ch; buf[0..plen]) {
661 if (ch != ' ') break;
662 ch = rchar;
665 //{ import core.stdc.stdio; printf("<{%s}>", buf); }
666 cwrxputstr!true(buf[0..plen], signw, lchar, rchar, wdt, maxwdt);
667 return;
673 private void cwrxputenum(TT) (TT nn, char signw, char lchar, char rchar, int wdt, int maxwdt) nothrow @trusted @nogc {
674 static if (is(TT == shared)) {
675 import core.atomic;
676 alias T = XUQQ!TT;
677 T n = atomicLoad(nn);
678 } else {
679 alias T = XUQQ!TT;
680 T n = nn;
683 foreach (string mname; __traits(allMembers, T)) {
684 if (n == __traits(getMember, T, mname)) {
685 cwrxputstr!false(mname, signw, lchar, rchar, wdt, maxwdt);
686 return;
689 static if (__traits(isUnsigned, T)) {
690 cwrxputint!long(cast(long)n, signw, lchar, rchar, wdt, maxwdt);
691 } else {
692 cwrxputint!ulong(cast(ulong)n, signw, lchar, rchar, wdt, maxwdt);
697 // ////////////////////////////////////////////////////////////////////////// //
698 /** write formatted string to console with runtime format string
699 * will try to not allocate if it is possible.
701 * understands [+|-]width[.maxlen]
702 * negative width: add spaces to right
703 * + signed width: center
704 * negative maxlen: get right part
705 * specifiers:
706 * 's': use to!string to write argument
707 * note that writer can print strings, bools, integrals and floats without allocation
708 * 'x': write integer as hex
709 * 'X': write integer as HEX
710 * '!': skip all arguments that's left, no width allowed
711 * '%': just a percent sign, no width allowed
712 * '|': print all arguments that's left with simple "%s", no width allowed
713 * '<...>': print all arguments that's left with simple "%s", delimited with "...", no width allowed
714 * options (must immediately follow '%'): NOT YET
715 * '~': fill with the following char instead of space
716 * second '~': right filling char for 'center'
718 public void conprintfX(bool donl, A...) (ConString fmt, in auto ref A args) {
719 import core.stdc.stdio : snprintf;
720 char[128] tmp = void;
721 usize tlen;
723 struct Writer {
724 void put (const(char)[] s...) nothrow @trusted @nogc {
725 foreach (char ch; s) {
726 if (tlen >= tmp.length) { tlen = tmp.length+42; break; }
727 tmp.ptr[tlen++] = ch;
732 static struct IntNum { char sign; int aval; bool leadingZero; /*|value|*/ }
734 static char[] utf8Encode (char[] s, dchar c) pure nothrow @trusted @nogc {
735 assert(s.length >= 4);
736 if (c > 0x10FFFF) c = '\uFFFD';
737 if (c <= 0x7F) {
738 s.ptr[0] = cast(char)c;
739 return s[0..1];
740 } else {
741 if (c <= 0x7FF) {
742 s.ptr[0] = cast(char)(0xC0|(c>>6));
743 s.ptr[1] = cast(char)(0x80|(c&0x3F));
744 return s[0..2];
745 } else if (c <= 0xFFFF) {
746 s.ptr[0] = cast(char)(0xE0|(c>>12));
747 s.ptr[1] = cast(char)(0x80|((c>>6)&0x3F));
748 s.ptr[2] = cast(char)(0x80|(c&0x3F));
749 return s[0..3];
750 } else if (c <= 0x10FFFF) {
751 s.ptr[0] = cast(char)(0xF0|(c>>18));
752 s.ptr[1] = cast(char)(0x80|((c>>12)&0x3F));
753 s.ptr[2] = cast(char)(0x80|((c>>6)&0x3F));
754 s.ptr[3] = cast(char)(0x80|(c&0x3F));
755 return s[0..4];
756 } else {
757 s[0..3] = "<?>";
758 return s[0..3];
763 void parseInt(bool allowsign) (ref IntNum n) nothrow @trusted @nogc {
764 n.sign = ' ';
765 n.aval = 0;
766 n.leadingZero = false;
767 if (fmt.length == 0) return;
768 static if (allowsign) {
769 if (fmt.ptr[0] == '-' || fmt.ptr[0] == '+') {
770 n.sign = fmt.ptr[0];
771 fmt = fmt[1..$];
772 if (fmt.length == 0) return;
775 if (fmt.ptr[0] < '0' || fmt.ptr[0] > '9') return;
776 n.leadingZero = (fmt.ptr[0] == '0');
777 while (fmt.length && fmt.ptr[0] >= '0' && fmt.ptr[0] <= '9') {
778 n.aval = n.aval*10+(fmt.ptr[0]-'0'); // ignore overflow here
779 fmt = fmt[1..$];
783 void skipLitPart () nothrow @trusted @nogc {
784 while (fmt.length > 0) {
785 import core.stdc.string : memchr;
786 auto prcptr = cast(const(char)*)memchr(fmt.ptr, '%', fmt.length);
787 if (prcptr is null) prcptr = fmt.ptr+fmt.length;
788 // print (and remove) literal part
789 if (prcptr !is fmt.ptr) {
790 auto lplen = cast(usize)(prcptr-fmt.ptr);
791 cwrxputch(fmt[0..lplen]);
792 fmt = fmt[lplen..$];
794 if (fmt.length >= 2 && fmt.ptr[0] == '%' && fmt.ptr[1] == '%') {
795 cwrxputch("%");
796 fmt = fmt[2..$];
797 continue;
799 break;
803 if (fmt.length == 0) return;
804 consoleWriteLock();
805 scope(exit) consoleWriteUnlock();
806 auto fmtanchor = fmt;
807 IntNum wdt, maxlen;
808 char mode = ' ';
810 ConString smpDelim = null;
811 ConString fmtleft = null;
813 argloop: foreach (immutable argnum, /*auto*/ att; A) {
814 alias at = XUQQ!att;
815 // skip literal part of the format string
816 skipLitPart();
817 // something's left in the format string?
818 if (fmt.length > 0) {
819 // here we should have at least two chars
820 assert(fmt[0] == '%');
821 if (fmt.length == 1) { cwrxputch("<stray-percent-in-conprintf>"); break argloop; }
822 fmt = fmt[1..$];
823 parseInt!true(wdt);
824 if (fmt.length && fmt[0] == '.') {
825 fmt = fmt[1..$];
826 parseInt!false(maxlen);
827 } else {
828 maxlen.sign = ' ';
829 maxlen.aval = 0;
830 maxlen.leadingZero = false;
832 if (fmt.length == 0) { cwrxputch("<stray-percent-in-conprintf>"); break argloop; }
833 mode = fmt[0];
834 fmt = fmt[1..$];
835 switch (mode) {
836 case '!': break argloop; // no more
837 case '|': // rock 'till the end
838 wdt.sign = maxlen.sign = ' ';
839 wdt.aval = maxlen.aval = 0;
840 wdt.leadingZero = maxlen.leadingZero = false;
841 if (fmt !is null) fmtleft = fmt;
842 fmt = null;
843 break;
844 case '<': // <...>: print all arguments that's left with simple "%s", delimited with "...", no width allowed
845 wdt.sign = maxlen.sign = ' ';
846 wdt.aval = maxlen.aval = 0;
847 wdt.leadingZero = maxlen.leadingZero = false;
848 usize epos = 0;
849 while (epos < fmt.length && fmt[epos] != '>') ++epos;
850 smpDelim = fmt[0..epos];
851 if (epos < fmt.length) ++epos;
852 fmtleft = fmt[epos..$];
853 fmt = null;
854 break;
855 case 's': // process as simple string
856 break;
857 case 'd': case 'D': // decimal, process as simple string
858 case 'u': case 'U': // unsigned decimal, process as simple string
859 static if (is(at == bool)) {
860 cwrxputint((args[argnum] ? 1 : 0), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
861 continue argloop;
862 } else static if (is(at == char) || is(at == wchar) || is(at == dchar)) {
863 cwrxputint(cast(uint)(args[argnum] ? 1 : 0), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
864 continue argloop;
865 } else static if (is(at == enum)) {
866 static if (at.min >= int.min && at.max <= int.max) {
867 cwrxputint(cast(int)args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
868 } else static if (at.min >= 0 && at.max <= uint.max) {
869 cwrxputint(cast(uint)args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
870 } else static if (at.min >= long.min && at.max <= long.max) {
871 cwrxputint(cast(long)args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
872 } else {
873 cwrxputint(cast(ulong)args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
875 continue argloop;
876 } else {
877 break;
879 case 'c': case 'C': // char
880 static if (is(at == bool)) {
881 cwrxputstr!false((args[argnum] ? "t" : "f"), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
882 } else static if (is(at == wchar) || is(at == dchar)) {
883 cwrxputstr!false(utf8Encode(tmp[], cast(dchar)args[argnum]), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
884 } else static if (is(at == byte) || is(at == short) || is(at == int) || is(at == long) ||
885 is(at == ubyte) || is(at == ushort) || is(at == uint) || is(at == ulong) ||
886 is(at == char))
888 tmp[0] = cast(char)args[argnum]; // nobody cares about unifuck
889 cwrxputstr!false(tmp[0..1], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
890 } else {
891 cwrxputch("<cannot-charprint-");
892 cwrxputch(at.stringof);
893 cwrxputch("in-conprintf>");
895 continue argloop;
896 case 'x': case 'X': // hex
897 static if (is(at == bool)) {
898 cwrxputint((args[argnum] ? 1 : 0), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
899 } else static if (is(at == byte) || is(at == short) || is(at == int) || is(at == long) ||
900 is(at == ubyte) || is(at == ushort) || is(at == uint) || is(at == ulong))
902 //cwrxputint(args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
903 cwrxputhex(args[argnum], (mode == 'X'), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
904 } else static if (is(at == char) || is(at == wchar) || is(at == dchar)) {
905 cwrxputhex(cast(uint)args[argnum], (mode == 'X'), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
906 } else static if (is(at == enum)) {
907 static if (at.min >= int.min && at.max <= int.max) {
908 cwrxputhex(cast(int)args[argnum], (mode == 'X'), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
909 } else static if (at.min >= 0 && at.max <= uint.max) {
910 cwrxputhex(cast(uint)args[argnum], (mode == 'X'), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
911 } else static if (at.min >= long.min && at.max <= long.max) {
912 cwrxputhex(cast(long)args[argnum], (mode == 'X'), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
913 } else {
914 cwrxputhex(cast(ulong)args[argnum], (mode == 'X'), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
916 } else static if (is(at : T*, T)) {
917 // pointers
918 cwrxputhex(cast(usize)args[argnum], (mode == 'X'), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
919 } else {
920 cwrxputch("<cannot-hexprint-");
921 cwrxputch(at.stringof);
922 cwrxputch("in-conprintf>");
924 continue argloop;
925 case 'f':
926 static if (is(at == bool)) {
927 cwrxputfloat((args[argnum] ? 1.0f : 0.0f), false, wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
928 } else static if (is(at == byte) || is(at == short) || is(at == int) || is(at == long) ||
929 is(at == ubyte) || is(at == ushort) || is(at == uint) || is(at == ulong) ||
930 is(at == char) || is(at == wchar) || is(at == dchar))
932 cwrxputfloat(cast(double)args[argnum], false, wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
933 } else static if (is(at == float) || is(at == double)) {
934 cwrxputfloat(args[argnum], false, wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
935 } else {
936 cwrxputch("<cannot-floatprint-");
937 cwrxputch(at.stringof);
938 cwrxputch("in-conprintf>");
940 continue argloop;
941 default:
942 cwrxputch("<invalid-format-in-conprintf(%");
943 if (mode < 32 || mode == 127) {
944 tlen = snprintf(tmp.ptr, tmp.length, "\\x%02x", cast(uint)mode);
945 cwrxputch(tmp[0..tlen]);
946 } else {
947 cwrxputch(mode);
949 cwrxputch(")>");
950 break argloop;
952 } else {
953 if (smpDelim.length) cwrxputch(smpDelim);
954 wdt.sign = maxlen.sign = ' ';
955 wdt.aval = maxlen.aval = 0;
956 wdt.leadingZero = maxlen.leadingZero = false;
958 // print as string
959 static if (is(at == bool)) {
960 cwrxputstr!false((args[argnum] ? "true" : "false"), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
961 } else static if (is(at == wchar) || is(at == dchar)) {
962 cwrxputstr!false(utf8Encode(tmp[], cast(dchar)args[argnum]), wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
963 } else static if (is(at == byte) || is(at == short) || is(at == int) || is(at == long) ||
964 is(at == ubyte) || is(at == ushort) || is(at == uint) || is(at == ulong))
966 cwrxputint(args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
967 } else static if (is(at == char)) {
968 tmp[0] = cast(char)args[argnum]; // nobody cares about unifuck
969 cwrxputstr!false(tmp[0..1], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
970 } else static if (is(at == float) || is(at == double)) {
971 cwrxputfloat(args[argnum], false, wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
972 } else static if (is(at == enum)) {
973 cwrxputenum(args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
974 } else static if (is(at : T*, T)) {
975 // pointers
976 static if (is(XUQQ!T == char)) {
977 // special case: `char*` will be printed as 0-terminated string
978 cwrxputstrz(args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
979 } else {
980 // other pointers will be printed as "0x..."
981 if (wdt.aval == 0) { wdt.aval = 8/*cast(int)usize.sizeof*2*/; wdt.leadingZero = true; }
982 cwrxputhex(cast(usize)args[argnum], true, wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
984 } else static if (is(at : const(char)[])) {
985 // strings
986 cwrxputstr!false(args[argnum], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
987 } else {
988 // alas
989 try {
990 import std.format : formatValue, singleSpec;
991 tlen = 0;
992 scope Writer wrt;
993 scope spec = singleSpec("%s");
994 formatValue(wrt, args[argnum], spec);
995 if (tlen > tmp.length) {
996 cwrxputch("<error-formatting-in-conprintf>");
997 } else {
998 cwrxputstr!false(tmp[0..tlen], wdt.sign, (wdt.leadingZero ? '0' : ' '), (maxlen.leadingZero ? '0' : ' '), wdt.aval, maxlen.aval);
1000 } catch (Exception e) {
1001 cwrxputch("<error-formatting-in-conprintf>");
1006 if (fmt is null) {
1007 if (fmtleft.length) cwrxputch(fmtleft);
1010 if (fmt.length) {
1011 skipLitPart();
1012 if (fmt.length) {
1013 assert(fmt[0] == '%');
1014 if (fmt.length == 1) {
1015 cwrxputch("<stray-percent-in-conprintf>");
1016 } else {
1017 fmt = fmt[1..$];
1018 parseInt!true(wdt);
1019 if (fmt.length && fmt[0] == '.') {
1020 fmt = fmt[1..$];
1021 parseInt!false(maxlen);
1023 if (fmt.length == 0) {
1024 cwrxputch("<stray-percent-in-conprintf>");
1025 } else {
1026 mode = fmt[0];
1027 fmt = fmt[1..$];
1028 if (mode == '!' || mode == '|') {
1029 if (fmt.length) cwrxputch(fmt);
1030 } else if (mode == '<') {
1031 while (fmt.length && fmt[0] != '>') fmt = fmt[1..$];
1032 if (fmt.length) fmt = fmt[1..$];
1033 if (fmt.length) cwrxputch(fmt);
1034 } else {
1035 cwrxputch("<orphan-format-in-conprintf(%");
1036 if (mode < 32 || mode == 127) {
1037 tlen = snprintf(tmp.ptr, tmp.length, "\\x%02x", cast(uint)mode);
1038 cwrxputch(tmp[0..tlen]);
1039 } else {
1040 cwrxputch(mode);
1042 cwrxputch(")>");
1049 static if (donl) cwrxputch("\n");
1052 public void conprintf(A...) (ConString fmt, in auto ref A args) { conprintfX!false(fmt, args); }
1053 public void conprintfln(A...) (ConString fmt, in auto ref A args) { conprintfX!true(fmt, args); }
1056 // ////////////////////////////////////////////////////////////////////////// //
1057 version(iv_cmdcon_honest_ctfe_writer)
1058 /// write formatted string to console with compile-time format string
1059 public template conwritef(string fmt, A...) {
1060 private string gen() () {
1061 usize pos;
1062 char[] res;
1063 usize respos;
1064 //string simpledelim;
1065 char[] simpledelim;
1066 uint simpledelimpos;
1068 res.length = fmt.length+128;
1070 void putRes (const(char)[] s...) {
1071 //if (respos+s.length > res.length) res.length = respos+s.length+256;
1072 foreach (char ch; s) {
1073 if (respos >= res.length) res.length = res.length*2;
1074 res[respos++] = ch;
1078 void putSD (const(char)[] s...) {
1079 if (simpledelim.length == 0) simpledelim.length = 128;
1080 foreach (char ch; s) {
1081 if (simpledelimpos >= simpledelim.length) simpledelim.length = simpledelim.length*2;
1082 simpledelim[simpledelimpos++] = ch;
1086 void putNum(bool hex=false) (int n, int minlen=-1) {
1087 if (n == n.min) assert(0, "oops");
1088 if (n < 0) { putRes('-'); n = -(n); }
1089 char[24] buf;
1090 int bpos = buf.length;
1091 do {
1092 static if (hex) {
1093 buf[--bpos] = "0123456789abcdef"[n&0x0f];
1094 n /= 16;
1095 } else {
1096 buf[--bpos] = cast(char)('0'+n%10);
1097 n /= 10;
1099 --minlen;
1100 } while (n != 0 || minlen > 0);
1101 putRes(buf[bpos..$]);
1104 int parseNum (ref char sign, ref bool leadzero) {
1105 sign = ' ';
1106 leadzero = false;
1107 if (pos >= fmt.length) return 0;
1108 if (fmt[pos] == '-' || fmt[pos] == '+') sign = fmt[pos++];
1109 if (pos >= fmt.length) return 0;
1110 int res = 0;
1111 if (pos < fmt.length && fmt[pos] == '0') leadzero = true;
1112 while (pos < fmt.length && fmt[pos] >= '0' && fmt[pos] <= '9') res = res*10+fmt[pos++]-'0';
1113 return res;
1116 void processUntilFSp () {
1117 while (pos < fmt.length) {
1118 usize epos = pos;
1119 while (epos < fmt.length && fmt[epos] != '%') {
1120 if (fmt[epos] < ' ' && fmt[epos] != '\t' && fmt[epos] != '\n') break;
1121 if (fmt[epos] >= 127 || fmt[epos] == '`') break;
1122 ++epos;
1124 if (epos > pos) {
1125 putRes("cwrxputch(`");
1126 putRes(fmt[pos..epos]);
1127 putRes("`);\n");
1128 pos = epos;
1129 if (pos >= fmt.length) break;
1131 if (fmt[pos] != '%') {
1132 putRes("cwrxputch('\\x");
1133 putNum!true(cast(ubyte)fmt[pos], 2);
1134 putRes("');\n");
1135 ++pos;
1136 continue;
1138 if (fmt.length-pos < 2 || fmt[pos+1] == '%') { putRes("cwrxputch('%');\n"); pos += 2; continue; }
1139 return;
1143 bool simples = false;
1144 bool putsimpledelim = false;
1145 char lchar=' ', rchar=' ';
1146 char signw = ' ';
1147 bool leadzerow = false;
1148 int wdt, maxwdt;
1149 char fmtch;
1151 argloop: foreach (immutable argnum, /*auto*/ att; A) {
1152 alias at = XUQQ!att;
1153 if (!simples) {
1154 processUntilFSp();
1155 if (pos >= fmt.length) assert(0, "out of format specifiers for arguments");
1156 assert(fmt[pos] == '%');
1157 ++pos;
1158 if (pos < fmt.length && fmt[pos] == '!') { ++pos; break; } // skip rest
1159 if (pos < fmt.length && fmt[pos] == '|') {
1160 ++pos;
1161 simples = true;
1162 } else if (pos < fmt.length && fmt[pos] == '<') {
1163 ++pos;
1164 simples = true;
1165 auto ep = pos;
1166 while (ep < fmt.length) {
1167 if (fmt[ep] == '>') break;
1168 if (fmt[ep] == '\\') ++ep;
1169 ++ep;
1171 if (ep >= fmt.length) assert(0, "invalid format string");
1172 simpledelimpos = 0;
1173 if (ep-pos > 0) {
1174 bool hasQuote = false;
1175 foreach (char ch; fmt[pos..ep]) if (ch == '\\' || (ch < ' ' && ch != '\n' && ch != '\t') || ch >= 127 || ch == '`') { hasQuote = true; break; }
1176 if (!hasQuote) {
1177 putSD("cwrxputch(`");
1178 putSD(fmt[pos..ep]);
1179 putSD("`);\n");
1180 } else {
1181 //FIXME: get rid of char-by-char processing!
1182 putSD("cwrxputch(\"");
1183 while (pos < ep) {
1184 char ch = fmt[pos++];
1185 if (ch == '\\') ch = fmt[pos++];
1186 if (ch == '\\' || ch < ' ' || ch >= 127 || ch == '"') {
1187 putSD("\\x");
1188 putSD("0123456789abcdef"[ch>>4]);
1189 putSD("0123456789abcdef"[ch&0x0f]);
1190 } else {
1191 putSD(ch);
1194 putSD("\");\n");
1197 pos = ep+1;
1198 } else {
1199 lchar = rchar = ' ';
1200 bool lrset = false;
1201 if (pos < fmt.length && fmt[pos] == '~') {
1202 lrset = true;
1203 if (fmt.length-pos < 2) assert(0, "invalid format string");
1204 lchar = fmt[pos+1];
1205 pos += 2;
1206 if (pos < fmt.length && fmt[pos] == '~') {
1207 if (fmt.length-pos < 2) assert(0, "invalid format string");
1208 rchar = fmt[pos+1];
1209 pos += 2;
1212 wdt = parseNum(signw, leadzerow);
1213 if (pos >= fmt.length) assert(0, "invalid format string");
1214 if (!lrset && leadzerow) lchar = '0';
1215 if (fmt[pos] == '.') {
1216 char mws;
1217 bool lzw;
1218 ++pos;
1219 maxwdt = parseNum(mws, lzw);
1220 if (mws == '-') maxwdt = -maxwdt;
1221 } else {
1222 maxwdt = 0;
1224 if (pos >= fmt.length) assert(0, "invalid format string");
1225 fmtch = fmt[pos++];
1228 if (simples) {
1229 lchar = rchar = signw = ' ';
1230 leadzerow = false;
1231 wdt = maxwdt = 0;
1232 fmtch = 's';
1233 if (putsimpledelim && simpledelimpos > 0) {
1234 putRes(simpledelim[0..simpledelimpos]);
1235 } else {
1236 putsimpledelim = true;
1239 switch (fmtch) {
1240 case 's':
1241 case 'd': // oops
1242 case 'u': // oops
1243 static if (is(at == char)) {
1244 putRes("cwrxputchar(args[");
1245 putNum(argnum);
1246 putRes("]");
1247 } else static if (is(at == wchar) || is(at == dchar)) {
1248 putRes("import std.conv : to; cwrxputstr(to!string(args[");
1249 putNum(argnum);
1250 putRes("])");
1251 } else static if (is(at == bool)) {
1252 putRes("cwrxputstr((args[");
1253 putNum(argnum);
1254 putRes("] ? `true` : `false`)");
1255 } else static if (is(at == enum)) {
1256 putRes("cwrxputenum(args[");
1257 putNum(argnum);
1258 putRes("]");
1259 } else static if (is(at == float) || is(at == double) || is(at == real)) {
1260 putRes("cwrxputfloat(args[");
1261 putNum(argnum);
1262 putRes("], true");
1263 } else static if (is(at : const(char)[])) {
1264 putRes("cwrxputstr(args[");
1265 putNum(argnum);
1266 putRes("]");
1267 } else static if (is(at : T*, T)) {
1268 //putRes("cwrxputch(`0x`); ");
1269 static if (is(T == char)) {
1270 putRes("cwrxputstrz(args[");
1271 putNum(argnum);
1272 putRes("]");
1273 } else {
1274 if (wdt < (void*).sizeof*2) { lchar = '0'; wdt = cast(int)((void*).sizeof)*2; signw = ' '; }
1275 putRes("cwrxputhex(cast(usize)args[");
1276 putNum(argnum);
1277 putRes("], false");
1279 } else static if (is(at : long)) {
1280 putRes("cwrxputint(args[");
1281 putNum(argnum);
1282 putRes("]");
1283 } else {
1284 putRes("import std.conv : to; cwrxputstr(to!string(args[");
1285 putNum(argnum);
1286 putRes("])");
1288 break;
1289 case 'x':
1290 static if (is(at == char) || is(at == wchar) || is(at == dchar)) {
1291 putRes("cwrxputhex(cast(uint)args[");
1292 putNum(argnum);
1293 putRes("], false");
1294 } else static if (is(at == bool)) {
1295 putRes("cwrxputstr((args[");
1296 putNum(argnum);
1297 putRes("] ? `1` : `0`)");
1298 } else static if (is(at == float) || is(at == double) || is(at == real)) {
1299 assert(0, "can't hexprint floats yet");
1300 } else static if (is(at : T*, T)) {
1301 if (wdt < (void*).sizeof*2) { lchar = '0'; wdt = cast(int)((void*).sizeof)*2; signw = ' '; }
1302 putRes("cwrxputhex(cast(usize)args[");
1303 putNum(argnum);
1304 putRes("], false");
1305 } else static if (is(at : long)) {
1306 putRes("cwrxputhex(args[");
1307 putNum(argnum);
1308 putRes("], false");
1309 } else {
1310 assert(0, "can't print '"~at.stringof~"' as hex");
1312 break;
1313 case 'X':
1314 static if (is(at == char) || is(at == wchar) || is(at == dchar)) {
1315 putRes("cwrxputhex(cast(uint)args[");
1316 putNum(argnum);
1317 putRes("], true");
1318 } else static if (is(at == bool)) {
1319 putRes("cwrxputstr((args[");
1320 putNum(argnum);
1321 putRes("] ? `1` : `0`)");
1322 } else static if (is(at == float) || is(at == double) || is(at == real)) {
1323 assert(0, "can't hexprint floats yet");
1324 } else static if (is(at : T*, T)) {
1325 if (wdt < (void*).sizeof*2) { lchar = '0'; wdt = cast(int)((void*).sizeof)*2; signw = ' '; }
1326 putRes("cwrxputhex(cast(usize)args[");
1327 putNum(argnum);
1328 putRes("], true");
1329 } else static if (is(at : long)) {
1330 putRes("cwrxputhex(args[");
1331 putNum(argnum);
1332 putRes("], true");
1333 } else {
1334 assert(0, "can't print '"~at.stringof~"' as hex");
1336 break;
1337 case 'f':
1338 static if (is(at == float) || is(at == double) || is(at == real)) {
1339 putRes("cwrxputfloat(args[");
1340 putNum(argnum);
1341 putRes("], false");
1342 } else {
1343 assert(0, "can't print '"~at.stringof~"' as float");
1345 break;
1346 default: assert(0, "invalid format specifier: '"~fmtch~"'");
1348 putRes(", '");
1349 putRes(signw);
1350 putRes("', '\\x");
1351 putNum!true(cast(uint)lchar, 2);
1352 putRes("', '\\x");
1353 putNum!true(cast(uint)rchar, 2);
1354 putRes("', ");
1355 putNum(wdt);
1356 putRes(", ");
1357 putNum(maxwdt);
1358 putRes(");\n");
1360 while (pos < fmt.length) {
1361 processUntilFSp();
1362 if (pos >= fmt.length) break;
1363 assert(fmt[pos] == '%');
1364 ++pos;
1365 if (pos < fmt.length && (fmt[pos] == '!' || fmt[pos] == '|')) { ++pos; continue; } // skip rest
1366 if (pos < fmt.length && fmt[pos] == '<') {
1367 // skip it
1368 while (pos < fmt.length) {
1369 if (fmt[pos] == '>') break;
1370 if (fmt[pos] == '\\') ++pos;
1371 ++pos;
1373 ++pos;
1374 continue;
1376 assert(0, "too many format specifiers");
1378 return res[0..respos];
1380 void conwritef (A args) {
1381 //enum code = gen();
1382 //pragma(msg, code);
1383 //pragma(msg, "===========");
1384 consoleWriteLock();
1385 scope(exit) consoleWriteUnlock();
1386 mixin(gen());
1391 // ////////////////////////////////////////////////////////////////////////// //
1392 public:
1394 //void conwritef(string fmt, A...) (A args) { fdwritef!(fmt)(args); }
1395 version(iv_cmdcon_honest_ctfe_writer) {
1396 void conwritefln(string fmt, A...) (A args) { conwritef!(fmt)(args); cwrxputch('\n'); } /// write formatted string to console with compile-time format string
1397 void conwrite(A...) (A args) { conwritef!("%|")(args); } /// write formatted string to console with compile-time format string
1398 void conwriteln(A...) (A args) { conwritef!("%|\n")(args); } /// write formatted string to console with compile-time format string
1399 } else {
1400 public void conwritef(string fmt, A...) (in auto ref A args) { conprintfX!false(fmt, args); } /// unhonest CTFE writer
1401 void conwritefln(string fmt, A...) (in auto ref A args) { conprintfX!true(fmt, args); } /// write formatted string to console with compile-time format string
1402 void conwrite(A...) (in auto ref A args) { conprintfX!false("%|", args); } /// write formatted string to console with compile-time format string
1403 void conwriteln(A...) (in auto ref A args) { conprintfX!false("%|\n", args); } /// write formatted string to console with compile-time format string
1407 // ////////////////////////////////////////////////////////////////////////// //
1408 version(conwriter_test)
1409 unittest {
1410 class A {
1411 override string toString () const { return "{A}"; }
1414 char[] n = ['x', 'y', 'z'];
1415 char[3] t = "def";//['d', 'e', 'f'];
1416 conwriter("========================\n");
1417 conwritef!"`%%`\n"();
1418 conwritef!"`%-3s`\n"(42);
1419 conwritef!"<`%3s`%%{str=%s}%|>\n"(cast(int)42, "[a]", new A(), n, t[]);
1420 //conwritefln!"<`%3@%3s`>%!"(cast(int)42, "[a]", new A(), n, t);
1421 //errwriteln("stderr");
1422 conwritefln!"`%-3s`"(42);
1423 //conwritefln!"`%!z%-2@%-3s`%!"(69, 42, 666);
1424 //conwritefln!"`%!%1@%-3s%!`"(69, 42, 666);
1425 //conwritefln!"`%!%-1@%+0@%-3s%!`"(69, 42, 666);
1426 conwritefln!"`%3.5s`"("a");
1427 conwritefln!"`%7.5s`"("abcdefgh");
1428 conwritef!"%|\n"(42, 666);
1429 conwritefln!"`%+10.5s`"("abcdefgh");
1430 conwritefln!"`%+10.-5s`"("abcdefgh");
1431 conwritefln!"`%~++10.-5s`"("abcdefgh");
1432 conwritefln!"`%~+~:+10.-5s`"("abcdefgh");
1433 //conwritef!"%\0<>\0|\n"(42, 666, 999);
1434 //conwritef!"%\0\t\0|\n"(42, 666, 999);
1435 conwritefln!"`%~*05s %~.5s`"(42, 666);
1436 conwritef!"`%s`\n"(t);
1437 conwritef!"`%08s`\n"("alice");
1438 conwritefln!"#%08x"(16396);
1439 conwritefln!"#%08X"(-16396);
1440 conwritefln!"#%02X"(-16385);
1441 conwritefln!"[%06s]"(-666);
1442 conwritefln!"[%06s]"(cast(long)0x8000_0000_0000_0000uL);
1443 conwritefln!"[%06x]"(cast(long)0x8000_0000_0000_0000uL);
1445 void wrflt () nothrow @nogc {
1446 conwriteln(42.666f);
1447 conwriteln(cast(double)42.666);
1449 wrflt();
1451 //immutable char *strz = "stringz\0s";
1452 //conwritefln!"[%S]"(strz);
1456 // ////////////////////////////////////////////////////////////////////////// //
1457 /// dump variables. use like this: `mixin condump!("x", "y")` ==> "x = 5, y = 3"
1458 mixin template condump (Names...) {
1459 auto _xdump_tmp_ = {
1460 import iv.cmdcon : conwrite;
1461 foreach (/*auto*/ i, /*auto*/ name; Names) conwrite(name, " = ", mixin(name), (i < Names.length-1 ? ", " : "\n"));
1462 return false;
1463 }();
1467 version(conwriter_test_dump)
1468 unittest {
1469 int x = 5;
1470 int y = 3;
1471 int z = 15;
1473 mixin condump!("x", "y"); // x = 5, y = 3
1474 mixin condump!("z"); // z = 15
1475 mixin condump!("x+y"); // x+y = 8
1476 mixin condump!("x+y < z"); // x+y < z = true
1478 conbufDump();
1482 // ////////////////////////////////////////////////////////////////////////// //
1483 // command console
1485 /// base console command class
1486 public class ConCommand {
1487 private:
1488 public import std.conv : ConvException, ConvOverflowException;
1489 import std.range;
1490 // this is hack to avoid allocating error exceptions
1491 // don't do that at home!
1493 __gshared ConvException exBadNum;
1494 __gshared ConvException exBadStr;
1495 __gshared ConvException exBadBool;
1496 __gshared ConvException exBadInt;
1497 __gshared ConvException exBadEnum;
1498 __gshared ConvOverflowException exIntOverflow;
1499 __gshared ConvException exBadHexEsc;
1500 __gshared ConvException exBadEscChar;
1501 __gshared ConvException exNoArg;
1502 __gshared ConvException exTooManyArgs;
1503 __gshared ConvException exBadArgType;
1505 shared static this () {
1506 exBadNum = new ConvException("invalid number");
1507 exBadStr = new ConvException("invalid string");
1508 exBadBool = new ConvException("invalid boolean");
1509 exBadInt = new ConvException("invalid integer number");
1510 exBadEnum = new ConvException("invalid enumeration value");
1511 exIntOverflow = new ConvOverflowException("overflow in integral conversion");
1512 exBadHexEsc = new ConvException("invalid hex escape");
1513 exBadEscChar = new ConvException("invalid escape char");
1514 exNoArg = new ConvException("argument expected");
1515 exTooManyArgs = new ConvException("too many arguments");
1516 exBadArgType = new ConvException("can't parse given argument type (internal error)");
1519 public:
1520 alias ArgCompleteCB = void delegate (ConCommand self); /// prototype for argument completion callback
1522 private:
1523 __gshared usize wordBufMem; // buffer for `getWord()`
1524 __gshared uint wordBufUsed, wordBufSize;
1525 ArgCompleteCB argcomplete; // this delegate will be called to do argument autocompletion
1527 static void wbReset () nothrow @trusted @nogc { pragma(inline, true); wordBufUsed = 0; }
1529 static void wbPut (const(char)[] s...) nothrow @trusted @nogc {
1530 if (s.length == 0) return;
1531 if (s.length > int.max/4) assert(0, "oops: console command too long");
1532 if (wordBufUsed+cast(uint)s.length > int.max/4) assert(0, "oops: console command too long");
1533 // grow wordbuf, if necessary
1534 if (wordBufUsed+cast(uint)s.length > wordBufSize) {
1535 import core.stdc.stdlib : realloc;
1536 wordBufSize = ((wordBufUsed+cast(uint)s.length)|0x3fff)+1;
1537 auto newmem = cast(usize)realloc(cast(void*)wordBufMem, wordBufSize);
1538 if (newmem == 0) assert(0, "cmdcon: out of memory");
1539 wordBufMem = cast(usize)newmem;
1541 assert(wordBufUsed+cast(uint)s.length <= wordBufSize);
1542 (cast(char*)(wordBufMem+wordBufUsed))[0..s.length] = s;
1543 wordBufUsed += cast(uint)s.length;
1546 static @property const(char)[] wordBuf () nothrow @trusted @nogc { pragma(inline, true); return (cast(char*)wordBufMem)[0..wordBufUsed]; }
1548 public:
1549 string name; ///
1550 string help; ///
1552 this (string aname, string ahelp=null) { name = aname; help = ahelp; } ///
1554 void showHelp () { conwriteln(name, " -- ", help); } ///
1556 /// can throw, yep
1557 /// cmdline doesn't contain command name
1558 void exec (ConString cmdline) {
1559 auto w = getWord(cmdline);
1560 if (w == "?") showHelp;
1563 public:
1564 final @property ConCommand completer (ArgCompleteCB cb) { version(aliced) pragma(inline, true); argcomplete = cb; return this; }
1565 final @property ArgCompleteCB completer () pure { version(aliced) pragma(inline, true); return argcomplete; }
1567 static:
1568 /// parse ch as digit in given base. return -1 if ch is not a valid digit.
1569 int digit(TC) (TC ch, uint base) pure nothrow @safe @nogc if (isSomeChar!TC) {
1570 int res = void;
1571 if (ch >= '0' && ch <= '9') res = ch-'0';
1572 else if (ch >= 'A' && ch <= 'Z') res = ch-'A'+10;
1573 else if (ch >= 'a' && ch <= 'z') res = ch-'a'+10;
1574 else return -1;
1575 return (res >= base ? -1 : res);
1578 /** get word from string.
1580 * it will correctly parse quoted strings.
1582 * note that next call to `getWord()` can destroy result.
1583 * returns `null` if there are no more words.
1584 * `*strtemp` will be `true` if temporary string storage was used.
1586 ConString getWord (ref ConString s, bool *strtemp=null) {
1587 wbReset();
1588 if (strtemp !is null) *strtemp = false;
1589 usize pos;
1590 while (s.length > 0 && s.ptr[0] <= ' ') s = s[1..$];
1591 if (s.length == 0) return null;
1592 // quoted string?
1593 if (s.ptr[0] == '"' || s.ptr[0] == '\'') {
1594 char qch = s.ptr[0];
1595 s = s[1..$];
1596 pos = 0;
1597 bool hasSpecial = false;
1598 while (pos < s.length && s.ptr[pos] != qch) {
1599 if (s.ptr[pos] == '\\') { hasSpecial = true; break; }
1600 ++pos;
1602 // simple quoted string?
1603 if (!hasSpecial) {
1604 auto res = s[0..pos];
1605 if (pos < s.length) ++pos; // skip closing quote
1606 s = s[pos..$];
1607 return res;
1609 if (strtemp !is null) *strtemp = true;
1610 //wordBuf.assumeSafeAppend.length = pos;
1611 //if (pos) wordBuf[0..pos] = s[0..pos];
1612 if (pos) wbPut(s[0..pos]);
1613 // process special chars
1614 while (pos < s.length && s.ptr[pos] != qch) {
1615 if (s.ptr[pos] == '\\' && s.length-pos > 1) {
1616 ++pos;
1617 switch (s.ptr[pos++]) {
1618 case '"': case '\'': case '\\': wbPut(s.ptr[pos-1]); break;
1619 case '0': wbPut('\x00'); break;
1620 case 'a': wbPut('\a'); break;
1621 case 'b': wbPut('\b'); break;
1622 case 'e': wbPut('\x1b'); break;
1623 case 'f': wbPut('\f'); break;
1624 case 'n': wbPut('\n'); break;
1625 case 'r': wbPut('\r'); break;
1626 case 't': wbPut('\t'); break;
1627 case 'v': wbPut('\v'); break;
1628 case 'x': case 'X':
1629 int n = 0;
1630 foreach (immutable _; 0..2) {
1631 if (pos >= s.length) throw exBadHexEsc;
1632 char c2 = s.ptr[pos++];
1633 if (digit(c2, 16) < 0) throw exBadHexEsc;
1634 n = n*16+digit(c2, 16);
1636 wbPut(cast(char)n);
1637 break;
1638 default: throw exBadEscChar;
1640 continue;
1642 wbPut(s.ptr[pos++]);
1644 if (pos < s.length) ++pos; // skip closing quote
1645 s = s[pos..$];
1646 return wordBuf;
1647 } else {
1648 // normal word
1649 pos = 0;
1650 while (pos < s.length && s.ptr[pos] > ' ') ++pos;
1651 auto res = s[0..pos];
1652 s = s[pos..$];
1653 return res;
1657 /// parse value of type T.
1658 T parseType(T) (ref ConString s) {
1659 import std.utf : byCodeUnit;
1660 alias UT = XUQQ!T;
1661 static if (is(UT == enum)) {
1662 // enum
1663 auto w = getWord(s);
1664 // case-sensitive
1665 foreach (string mname; __traits(allMembers, UT)) {
1666 if (mname == w) return __traits(getMember, UT, mname);
1668 // case-insensitive
1669 foreach (string mname; __traits(allMembers, UT)) {
1670 if (strEquCI(mname, w)) return __traits(getMember, UT, mname);
1672 // integer
1673 if (w.length < 1) throw exBadEnum;
1674 auto ns = w;
1675 try {
1676 if (w[0] == '-') {
1677 long num = parseInt!long(ns);
1678 if (ns.length > 0) throw exBadEnum;
1679 foreach (string mname; __traits(allMembers, UT)) {
1680 if (__traits(getMember, UT, mname) == num) return __traits(getMember, UT, mname);
1682 } else {
1683 ulong num = parseInt!ulong(ns);
1684 if (ns.length > 0) throw exBadEnum;
1685 foreach (string mname; __traits(allMembers, UT)) {
1686 if (__traits(getMember, UT, mname) == num) return __traits(getMember, UT, mname);
1689 } catch (Exception) {}
1690 throw exBadEnum;
1691 } else static if (is(UT == bool)) {
1692 // boolean
1693 auto w = getWord(s);
1694 if (w is null) throw exNoArg;
1695 bool good = false;
1696 auto res = parseBool(w, true, &good);
1697 if (!good) throw exBadBool;
1698 return res;
1699 } else static if ((isIntegral!UT || isFloatingPoint!UT) && !is(UT == enum)) {
1700 // number
1701 auto w = getWord(s);
1702 if (w is null) throw exNoArg;
1703 bool goodbool = false;
1704 auto bv = parseBool(w, false, &goodbool); // no numbers
1705 if (goodbool) {
1706 return cast(UT)(bv ? 1 : 0);
1707 } else {
1708 auto ss = w.byCodeUnit;
1709 auto res = parseNum!UT(ss);
1710 if (!ss.empty) throw exBadNum;
1711 return res;
1713 } else static if (is(UT : ConString)) {
1714 // string
1715 bool stemp = false;
1716 auto w = getWord(s);
1717 if (w is null) throw exNoArg;
1718 if (s.length && s.ptr[0] > 32) throw exBadStr;
1719 if (stemp) {
1720 // temp storage was used
1721 static if (is(UT == string)) return w.idup; else return w.dup;
1722 } else {
1723 // no temp storage was used
1724 static if (is(UT == ConString)) return w;
1725 else static if (is(UT == string)) return w.idup;
1726 else return w.dup;
1728 } else static if (is(UT : char)) {
1729 // char
1730 bool stemp = false;
1731 auto w = getWord(s);
1732 if (w is null || w.length != 1) throw exNoArg;
1733 if (s.length && s.ptr[0] > 32) throw exBadStr;
1734 return w.ptr[0];
1735 } else {
1736 throw exBadArgType;
1740 /// parse boolean value
1741 public static bool parseBool (ConString s, bool allowNumbers=true, bool* goodval=null) nothrow @trusted @nogc {
1742 char[5] tbuf;
1743 if (goodval !is null) *goodval = false;
1744 while (s.length > 0 && s[0] <= ' ') s = s[1..$];
1745 while (s.length > 0 && s[$-1] <= ' ') s = s[0..$-1];
1746 if (s.length > tbuf.length) return false;
1747 usize pos = 0;
1748 foreach (char ch; s) {
1749 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower
1750 tbuf.ptr[pos++] = ch;
1752 switch (tbuf[0..pos]) {
1753 case "y": case "t":
1754 case "yes": case "tan":
1755 case "true": case "on":
1756 if (goodval !is null) *goodval = true;
1757 return true;
1758 case "1": case "-1": case "42":
1759 if (allowNumbers) {
1760 if (goodval !is null) *goodval = true;
1761 return true;
1763 break;
1764 case "n": case "f":
1765 case "no": case "ona":
1766 case "false": case "off":
1767 if (goodval !is null) *goodval = true;
1768 return false;
1769 case "0":
1770 if (allowNumbers) {
1771 if (goodval !is null) *goodval = true;
1772 return false;
1774 break;
1775 default: break;
1777 return false;
1780 /** parse integer number.
1782 * parser checks for overflows and understands different bases (0x, 0b, 0o, 0d).
1783 * parser skips leading spaces. stops on first non-numeric char.
1785 * Params:
1786 * T = result type
1787 * s = input range; will be modified
1789 * Returns:
1790 * parsed number
1792 * Throws:
1793 * ConvException or ConvOverflowException
1795 public static T parseInt(T, TS) (ref TS s) if (isSomeChar!(ElementType!TS) && isIntegral!T && !is(T == enum)) {
1796 import std.traits : isSigned;
1797 uint base = 10;
1798 ulong num = 0;
1799 static if (isSigned!T) bool neg = false;
1800 // skip spaces
1801 while (!s.empty) {
1802 if (s.front > 32) break;
1803 s.popFront();
1805 if (s.empty) throw exBadInt;
1806 // check for sign
1807 switch (s.front) {
1808 case '+': // it's ok
1809 s.popFront();
1810 break;
1811 case '-':
1812 static if (isSigned!T) {
1813 neg = true;
1814 s.popFront();
1815 break;
1816 } else {
1817 throw exBadInt;
1819 default: // do nothing
1821 if (s.empty) throw exBadInt;
1822 // check for various bases
1823 if (s.front == '0') {
1824 s.popFront();
1825 if (s.empty) return cast(T)0;
1826 auto ch = s.front;
1827 switch (/*auto ch = s.front*/ch) {
1828 case 'b': case 'B': base = 2; goto gotbase;
1829 case 'o': case 'O': base = 8; goto gotbase;
1830 case 'd': case 'D': base = 10; goto gotbase;
1831 case 'x': case 'X': base = 16;
1832 gotbase:
1833 s.popFront();
1834 goto checkfirstdigit;
1835 default:
1836 if (ch != '_' && digit(ch, base) < 0) throw exBadInt;
1837 break;
1839 } else {
1840 // no base specification; we want at least one digit
1841 checkfirstdigit:
1842 if (s.empty || digit(s.front, base) < 0) throw exBadInt;
1844 // parse number
1845 // we already know that the next char is valid
1846 bool needDigit = false;
1847 do {
1848 auto ch = s.front;
1849 int d = digit(ch, base);
1850 if (d < 0) {
1851 if (needDigit) throw exBadInt;
1852 if (ch != '_') break;
1853 needDigit = true;
1854 } else {
1855 // funny overflow checks
1856 auto onum = num;
1857 if ((num *= base) < onum) throw exIntOverflow;
1858 if ((num += d) < onum) throw exIntOverflow;
1859 needDigit = false;
1861 s.popFront();
1862 } while (!s.empty);
1863 if (needDigit) throw exBadInt;
1864 // check underflow and overflow
1865 static if (isSigned!T) {
1866 long n = cast(long)num;
1867 if (neg) {
1868 // special case: negative 0x8000_0000_0000_0000uL is ok
1869 if (num > 0x8000_0000_0000_0000uL) throw exIntOverflow;
1870 if (num != 0x8000_0000_0000_0000uL) n = -(n);
1871 } else {
1872 if (num >= 0x8000_0000_0000_0000uL) throw exIntOverflow;
1874 if (n < T.min || n > T.max) throw exIntOverflow;
1875 return cast(T)n;
1876 } else {
1877 if (num < T.min || num > T.max) throw exIntOverflow;
1878 return cast(T)num;
1882 /** parse number.
1884 * parser checks for overflows and understands different integer bases (0x, 0b, 0o, 0d).
1885 * parser skips leading spaces. stops on first non-numeric char.
1887 * Params:
1888 * T = result type
1889 * s = input range; will be modified
1891 * Returns:
1892 * parsed number
1894 * Throws:
1895 * ConvException or ConvOverflowException
1897 public static T parseNum(T, TS) (ref TS s) if (isSomeChar!(ElementType!TS) && (isIntegral!T || isFloatingPoint!T) && !is(T == enum)) {
1898 static if (isIntegral!T) {
1899 return parseInt!T(s);
1900 } else {
1901 while (!s.empty) {
1902 if (s.front > 32) break;
1903 s.popFront();
1905 import std.conv : stcparse = parse;
1906 return stcparse!T(s);
1910 public static bool checkHelp (scope ConString s) {
1911 usize pos = 0;
1912 while (pos < s.length && s.ptr[pos] <= 32) ++pos;
1913 if (pos == s.length || s.ptr[pos] != '?') return false;
1914 ++pos;
1915 while (pos < s.length && s.ptr[pos] <= 32) ++pos;
1916 return (pos >= s.length);
1919 public static bool hasArgs (scope ConString s) {
1920 usize pos = 0;
1921 while (pos < s.length && s.ptr[pos] <= 32) ++pos;
1922 return (pos < s.length);
1925 private static bool isBadStrChar() (char ch) {
1926 pragma(inline, true);
1927 return (ch < ' ' || ch == '\\' || ch == '"' || ch == '\'' || ch == '#' || ch == ';' || ch > 126);
1930 public static void writeQuotedString(bool forceQuote=true) (scope ConString s) { quoteStringDG!forceQuote(s, delegate (scope ConString s) { conwriter(s); }); }
1932 public static void quoteStringDG(bool forceQuote=false) (scope ConString s, scope void delegate (scope ConString) dg) {
1933 if (dg is null) return;
1934 static immutable string hexd = "0123456789abcdef";
1935 static bool isBadStrChar() (char ch) {
1936 pragma(inline, true);
1937 return (ch < ' ' || ch == '\\' || ch == '"' || ch == '\'' || ch == '#' || ch == ';' || ch > 126);
1939 // need to quote?
1940 if (s.length == 0) { dg(`""`); return; }
1941 static if (!forceQuote) {
1942 bool needQuote = false;
1943 foreach (char ch; s) if (ch == ' ' || isBadStrChar(ch)) { needQuote = true; break; }
1944 if (!needQuote) { dg(s); return; }
1946 dg("\"");
1947 usize pos = 0;
1948 while (pos < s.length) {
1949 usize end = pos;
1950 while (end < s.length && !isBadStrChar(s.ptr[end])) ++end;
1951 if (end > pos) dg(s[pos..end]);
1952 pos = end;
1953 if (pos >= s.length) break;
1954 dg("\\");
1955 switch (s.ptr[pos++]) {
1956 case '"': case '\'': case '\\': dg(s.ptr[pos-1..pos]); break;
1957 case '\x00': dg("0"); break;
1958 case '\a': dg("a"); break;
1959 case '\b': dg("b"); break;
1960 case '\x1b': dg("e"); break;
1961 case '\f': dg("f"); break;
1962 case '\n': dg("n"); break;
1963 case '\r': dg("r"); break;
1964 case '\t': dg("t"); break;
1965 case '\v': dg("c"); break;
1966 default:
1967 ubyte c = cast(ubyte)(s.ptr[pos-1]);
1968 dg("x");
1969 dg(hexd[c>>4..(c>>4)+1]);
1970 dg(hexd[c&0x0f..(c&0x0f)+1]);
1971 break;
1974 dg("\"");
1979 version(contest_parser) unittest {
1980 auto cc = new ConCommand("!");
1981 string s = "this is 'a test' string \"you\tknow\" ";
1982 auto sc = cast(ConString)s;
1984 auto w = cc.getWord(sc);
1985 assert(w == "this");
1988 auto w = cc.getWord(sc);
1989 assert(w == "is");
1992 auto w = cc.getWord(sc);
1993 assert(w == "a test");
1996 auto w = cc.getWord(sc);
1997 assert(w == "string");
2000 auto w = cc.getWord(sc);
2001 assert(w == "you\tknow");
2004 auto w = cc.getWord(sc);
2005 assert(w is null);
2006 assert(sc.length == 0);
2009 import std.conv : ConvException, ConvOverflowException;
2010 import std.exception;
2011 import std.math : abs;
2013 void testnum(T) (string s, T res, int line=__LINE__) {
2014 import std.string : format;
2015 bool ok = false;
2016 try {
2017 import std.utf : byCodeUnit;
2018 auto ss = s.byCodeUnit;
2019 auto v = ConCommand.parseNum!T(ss);
2020 while (!ss.empty && ss.front <= 32) ss.popFront();
2021 if (!ss.empty) throw new ConvException("shit happens!");
2022 static assert(is(typeof(v) == T));
2023 static if (isIntegral!T) ok = (v == res); else ok = (abs(v-res) < T.epsilon);
2024 } catch (ConvException e) {
2025 assert(0, format("unexpected exception thrown, called from line %s", line));
2027 if (!ok) assert(0, format("assertion failure, called from line %s", line));
2030 void testbadnum(T) (string s, int line=__LINE__) {
2031 import std.string : format;
2032 try {
2033 import std.utf : byCodeUnit;
2034 auto ss = s.byCodeUnit;
2035 auto v = ConCommand.parseNum!T(ss);
2036 while (!ss.empty && ss.front <= 32) ss.popFront();
2037 if (!ss.empty) throw new ConvException("shit happens!");
2038 } catch (ConvException e) {
2039 return;
2041 assert(0, format("exception not thrown, called from line %s", line));
2044 testnum!int(" -42", -42);
2045 testnum!int(" +42", 42);
2046 testnum!int(" -4_2", -42);
2047 testnum!int(" +4_2", 42);
2048 testnum!int(" -0d42", -42);
2049 testnum!int(" +0d42", 42);
2050 testnum!int("0x2a", 42);
2051 testnum!int("-0x2a", -42);
2052 testnum!int("0o52", 42);
2053 testnum!int("-0o52", -42);
2054 testnum!int("0b00101010", 42);
2055 testnum!int("-0b00101010", -42);
2056 testnum!ulong("+9223372036854775808", 9223372036854775808uL);
2057 testnum!long("9223372036854775807", 9223372036854775807);
2058 testnum!long("-9223372036854775808", -9223372036854775808uL); // uL to workaround issue #13606
2059 testnum!ulong("+0x8000_0000_0000_0000", 9223372036854775808uL);
2060 testnum!long("-0x8000_0000_0000_0000", -9223372036854775808uL); // uL to workaround issue #13606
2061 testbadnum!long("9223372036854775808");
2062 testbadnum!int("_42");
2063 testbadnum!int("42_");
2064 testbadnum!int("42_ ");
2065 testbadnum!int("4__2");
2066 testbadnum!int("0x_2a");
2067 testbadnum!int("-0x_2a");
2068 testbadnum!int("_0x2a");
2069 testbadnum!int("-_0x2a");
2070 testbadnum!int("_00x2a");
2071 testbadnum!int("-_00x2a");
2072 testbadnum!int(" +0x");
2074 testnum!int("666", 666);
2075 testnum!int("+666", 666);
2076 testnum!int("-666", -666);
2078 testbadnum!int("+");
2079 testbadnum!int("-");
2080 testbadnum!int("5a");
2082 testbadnum!int("5.0");
2083 testbadnum!int("5e+2");
2085 testnum!uint("666", 666);
2086 testnum!uint("+666", 666);
2087 testbadnum!uint("-666");
2089 testnum!int("0x29a", 666);
2090 testnum!int("0X29A", 666);
2091 testnum!int("-0x29a", -666);
2092 testnum!int("-0X29A", -666);
2093 testnum!int("0b100", 4);
2094 testnum!int("0B100", 4);
2095 testnum!int("-0b100", -4);
2096 testnum!int("-0B100", -4);
2097 testnum!int("0o666", 438);
2098 testnum!int("0O666", 438);
2099 testnum!int("-0o666", -438);
2100 testnum!int("-0O666", -438);
2101 testnum!int("0d666", 666);
2102 testnum!int("0D666", 666);
2103 testnum!int("-0d666", -666);
2104 testnum!int("-0D666", -666);
2106 testnum!byte("-0x7f", -127);
2107 testnum!byte("-0x80", -128);
2108 testbadnum!byte("0x80");
2109 testbadnum!byte("-0x81");
2111 testbadnum!uint("1a");
2112 testbadnum!uint("0x1g");
2113 testbadnum!uint("0b12");
2114 testbadnum!uint("0o78");
2115 testbadnum!uint("0d1f");
2117 testbadnum!int("0x_2__9_a__");
2118 testbadnum!uint("0x_");
2120 testnum!ulong("0x8000000000000000", 0x8000000000000000UL);
2121 testnum!long("-0x8000000000000000", -0x8000000000000000uL);
2122 testbadnum!long("0x8000000000000000");
2123 testbadnum!ulong("0x80000000000000001");
2124 testbadnum!long("-0x8000000000000001");
2126 testbadnum!float("-0O666");
2127 testnum!float("0x666p0", 1638.0f);
2128 testnum!float("-0x666p0", -1638.0f);
2129 testnum!double("+1.1e+2", 110.0);
2130 testnum!double("2.4", 2.4);
2131 testnum!double("1_2.4", 12.4);
2132 testnum!float("42666e-3", 42.666f);
2133 testnum!float(" 4.2 ", 4.2);
2135 conwriteln("console: parser test passed");
2139 // ////////////////////////////////////////////////////////////////////////// //
2140 /// console alias
2141 public final class ConAlias {
2142 private:
2143 public import std.conv : ConvOverflowException;
2144 __gshared ConvOverflowException exAliasTooBig;
2145 __gshared ConvOverflowException exAliasTooDeep;
2147 shared static this () {
2148 exAliasTooBig = new ConvOverflowException("alias text too big");
2149 exAliasTooDeep = new ConvOverflowException("alias expansion too deep");
2152 usize textmem;
2153 uint textsize;
2154 uint allocsize;
2156 this (const(char)[] atext) @nogc {
2157 set(atext);
2160 ~this () {
2161 import core.stdc.stdlib : free;
2162 if (textmem != 0) free(cast(void*)textmem);
2165 const(char)[] get () nothrow @trusted @nogc { pragma(inline, true); return (textmem != 0 ? (cast(char*)textmem)[0..textsize] : null); }
2167 void set (const(char)[] atext) @nogc {
2168 if (atext.length > 65535) throw exAliasTooBig;
2169 // realloc
2170 if (atext.length > allocsize) {
2171 import core.stdc.stdlib : realloc;
2172 uint newsz;
2173 if (atext.length <= 4096) newsz = 4096;
2174 else if (atext.length <= 8192) newsz = 8192;
2175 else if (atext.length <= 16384) newsz = 16384;
2176 else if (atext.length <= 32768) newsz = 32768;
2177 else newsz = 65536;
2178 assert(newsz >= atext.length);
2179 auto newmem = cast(usize)realloc(cast(void*)textmem, newsz);
2180 if (newmem == 0) assert(0, "out of memory for alias"); //FIXME
2181 textmem = newmem;
2182 allocsize = newsz;
2184 assert(allocsize >= atext.length);
2185 textsize = cast(uint)atext.length;
2186 (cast(char*)textmem)[0..textsize] = atext;
2191 // ////////////////////////////////////////////////////////////////////////// //
2192 /// convar attributes
2193 public enum ConVarAttr : uint {
2194 None = 0, ///
2195 Archive = 1U<<0, /// save on change (saving must be done by library user)
2196 Hex = 1U<<1, /// dump this variable as hex value (valid for integrals)
2197 //TODO:
2198 User = 1U<<30, /// user-created
2199 ReadOnly = 1U<<31, /// can't be changed with console command (direct change is still possible)
2203 /// variable of some type
2204 public class ConVarBase : ConCommand {
2205 protected:
2206 uint mAttrs;
2208 public:
2209 alias PreChangeHookCB = bool delegate (ConVarBase self, ConString newval); /// prototype for "before value change hook"; return `false` to abort; `newval` is not parsed
2210 alias PostChangeHookCB = void delegate (ConVarBase self, ConString newval); /// prototype for "after value change hook"; `newval` is not parsed
2212 private:
2213 PreChangeHookCB hookBeforeChange;
2214 PostChangeHookCB hookAfterChange;
2216 public:
2217 this (string aname, string ahelp=null) { super(aname, ahelp); } ///
2219 /// replaces current attributes (can't set `ConVarAttr.User` w/o !true)
2220 final void setAttrs(bool any=false) (const(ConVarAttr)[] attrs...) pure nothrow @safe @nogc {
2221 mAttrs = 0;
2222 foreach (const ConVarAttr a; attrs) {
2223 static if (!any) {
2224 if (a == ConVarAttr.User) continue;
2226 mAttrs |= a;
2230 final ConVarBase setBeforeHook (PreChangeHookCB cb) { hookBeforeChange = cb; return this; } /// this can be chained
2231 final ConVarBase setAfterHook (PostChangeHookCB cb) { hookAfterChange = cb; return this; } /// this can be chained
2233 @property pure nothrow @safe @nogc final {
2234 PreChangeHookCB beforeHook () { return hookBeforeChange; } ///
2235 PostChangeHookCB afterHook () { return hookAfterChange; } ///
2237 void beforeHook (PreChangeHookCB cb) { hookBeforeChange = cb; } ///
2238 void afterHook (PostChangeHookCB cb) { hookAfterChange = cb; } ///
2240 /// attributes (see ConVarAttr enum)
2241 uint attrs () const { pragma(inline, true); return mAttrs; }
2242 /// replaces current attributes (can't set/remove `ConVarAttr.User` w/o !true)
2243 void attrs(bool any=false) (uint v) {
2244 pragma(inline, true);
2245 static if (any) mAttrs = v; else mAttrs = v&~(ConVarAttr.User);
2248 bool attrArchive () const { pragma(inline, true); return ((mAttrs&ConVarAttr.Archive) != 0); } ///
2249 bool attrNoArchive () const { pragma(inline, true); return ((mAttrs&ConVarAttr.Archive) == 0); } ///
2251 void attrArchive (bool v) { pragma(inline, true); if (v) mAttrs |= ConVarAttr.Archive; else mAttrs &= ~ConVarAttr.Archive; } ///
2252 void attrNoArchive (bool v) { pragma(inline, true); if (!v) mAttrs |= ConVarAttr.Archive; else mAttrs &= ~ConVarAttr.Archive; } ///
2254 bool attrHexDump () const { pragma(inline, true); return ((mAttrs&ConVarAttr.Hex) != 0); } ///
2256 bool attrReadOnly () const { pragma(inline, true); return ((mAttrs&ConVarAttr.ReadOnly) != 0); } ///
2257 void attrReadOnly (bool v) { pragma(inline, true); if (v) mAttrs |= ConVarAttr.ReadOnly; else mAttrs &= ~ConVarAttr.ReadOnly; } ///
2259 bool attrUser () const { pragma(inline, true); return ((mAttrs&ConVarAttr.User) != 0); } ///
2262 abstract void printValue (); /// print variable value to console
2263 abstract bool isString () const pure nothrow @nogc; /// is this string variable?
2264 abstract ConString strval () /*nothrow @nogc*/; /// get string value (for any var)
2266 /// get variable value, converted to the given type (if it is possible).
2267 @property T value(T) () /*nothrow @nogc*/ {
2268 pragma(inline, true);
2269 static if (is(T == enum)) {
2270 return cast(T)getIntValue;
2271 } else static if (is(T : ulong)) {
2272 // integer, xchar, boolean
2273 return cast(T)getIntValue;
2274 } else static if (is(T : double)) {
2275 // floats
2276 return cast(T)getDoubleValue;
2277 } else static if (is(T : ConString)) {
2278 // string
2279 static if (is(T == string)) return strval.idup;
2280 else static if (is(T == char[])) return strval.dup;
2281 else return strval;
2282 } else {
2283 // alas
2284 return T.init;
2288 /// set variable value, converted from the given type (if it is possible).
2289 /// ReadOnly is ignored, no hooks will be called.
2290 @property void value(T) (T val) /*nothrow*/ {
2291 pragma(inline, true);
2292 static if (is(T : ulong)) {
2293 // integer, xchar, boolean
2294 setIntValue(cast(ulong)val, isSigned!T);
2295 } else static if (is(T : double)) {
2296 // floats
2297 setDoubleValue(cast(double)val);
2298 } else static if (is(T : ConString)) {
2299 static if (is(T == string)) setStrValue(val); else setCCharValue(val);
2300 } else {
2301 static assert(0, "invalid type");
2305 protected:
2306 abstract ulong getIntValue () /*nothrow @nogc*/;
2307 abstract double getDoubleValue () /*nothrow @nogc*/;
2309 abstract void setIntValue (ulong v, bool signed) /*nothrow @nogc*/;
2310 abstract void setDoubleValue (double v) /*nothrow @nogc*/;
2311 abstract void setStrValue (string v) /*nothrow*/;
2312 abstract void setCCharValue (ConString v) /*nothrow*/;
2316 // ////////////////////////////////////////////////////////////////////////// //
2317 /// console will use this to register console variables
2318 final class ConVar(T) : ConVarBase {
2319 alias TT = XUQQ!T;
2320 enum useAtomic = is(T == shared);
2321 T* vptr;
2322 static if (isIntegral!TT) {
2323 TT minv = TT.min;
2324 TT maxv = TT.max;
2325 } else static if (isFloatingPoint!TT) {
2326 TT minv = -TT.max;
2327 TT maxv = TT.max;
2329 static if (!is(TT : ConString)) {
2330 char[256] vbuf;
2331 } else {
2332 char[256] tvbuf; // temp value
2335 TT delegate (ConVarBase self) cvGetter;
2336 void delegate (ConVarBase self, TT nv) cvSetter;
2338 void delegate (ConVarBase self, TT pv, TT nv) hookAfterChangeVV;
2340 this (T* avptr, string aname, string ahelp=null) {
2341 vptr = avptr;
2342 super(aname, ahelp);
2345 static if (isIntegral!TT || isFloatingPoint!TT) {
2346 this (T* avptr, TT aminv, TT amaxv, string aname, string ahelp=null) {
2347 vptr = avptr;
2348 minv = aminv;
2349 maxv = amaxv;
2350 super(aname, ahelp);
2354 /// this method will respect `ReadOnly` flag, and will call before/after hooks.
2355 override void exec (ConString cmdline) {
2356 if (checkHelp(cmdline)) { showHelp; return; }
2357 if (!hasArgs(cmdline)) { printValue; return; }
2358 if (attrReadOnly) return; // can't change read-only var with console commands
2359 static if ((is(TT == bool) || isIntegral!TT || isFloatingPoint!TT) && !is(TT == enum)) {
2360 while (cmdline.length && cmdline[0] <= 32) cmdline = cmdline[1..$];
2361 while (cmdline.length && cmdline[$-1] <= 32) cmdline = cmdline[0..$-1];
2362 if (cmdline == "toggle") {
2363 if (hookBeforeChange !is null) { if (!hookBeforeChange(this, cmdline)) return; }
2364 auto prval = getv();
2365 if (cvSetter !is null) {
2366 cvSetter(this, !prval);
2367 } else {
2368 static if (useAtomic) {
2369 import core.atomic;
2370 atomicStore(*vptr, !prval);
2371 } else {
2372 *vptr = !prval;
2375 if (hookAfterChangeVV !is null) hookAfterChangeVV(this, prval, !prval);
2376 if (hookAfterChange !is null) hookAfterChange(this, cmdline);
2377 return;
2380 auto newvals = cmdline;
2381 TT val = parseType!TT(/*ref*/ cmdline);
2382 if (hasArgs(cmdline)) throw exTooManyArgs;
2383 static if (isIntegral!TT) {
2384 if (val < minv) val = minv;
2385 if (val > maxv) val = maxv;
2387 if (hookBeforeChange !is null) { if (!hookBeforeChange(this, newvals)) return; }
2388 TT oval;
2389 if (hookAfterChangeVV !is null) oval = getv();
2390 if (cvSetter !is null) {
2391 cvSetter(this, val);
2392 } else {
2393 static if (useAtomic) {
2394 import core.atomic;
2395 atomicStore(*vptr, val);
2396 } else {
2397 *vptr = val;
2400 if (hookAfterChangeVV !is null) hookAfterChangeVV(this, oval, val);
2401 if (hookAfterChange !is null) hookAfterChange(this, newvals);
2404 override bool isString () const pure nothrow @nogc {
2405 static if (is(TT : ConString)) return true; else return false;
2408 final private TT getv() () /*nothrow @nogc*/ {
2409 pragma(inline, true);
2410 import core.atomic;
2411 if (cvGetter !is null) {
2412 return cvGetter(this);
2413 } else {
2414 static if (useAtomic) return atomicLoad(*vptr); else return *vptr;
2418 override ConString strval () /*nothrow @nogc*/ {
2419 //conwriteln("*** strval for '", name, "'");
2420 import core.stdc.stdio : snprintf;
2421 static if (is(TT == enum)) {
2422 auto v = getv();
2423 foreach (string mname; __traits(allMembers, TT)) {
2424 if (__traits(getMember, TT, mname) == v) return mname;
2426 return "???";
2427 } else static if (is(T : ConString)) {
2428 return getv();
2429 } else static if (is(XUQQ!T == bool)) {
2430 return (getv() ? "tan" : "ona");
2431 } else static if (isIntegral!T) {
2432 static if (isSigned!T) {
2433 auto len = snprintf(vbuf.ptr, vbuf.length, "%lld", cast(long)(getv()));
2434 } else {
2435 auto len = snprintf(vbuf.ptr, vbuf.length, (attrHexDump ? "0x%06llx".ptr : "%llu".ptr), cast(ulong)(getv()));
2437 return (len >= 0 ? vbuf[0..len] : "?");
2438 } else static if (isFloatingPoint!T) {
2439 auto len = snprintf(vbuf.ptr, vbuf.length, "%g", cast(double)(getv()));
2440 return (len >= 0 ? vbuf[0..len] : "?");
2441 } else static if (is(XUQQ!T == char)) {
2442 vbuf.ptr[0] = cast(char)getv();
2443 return vbuf[0..1];
2444 } else {
2445 static assert(0, "can't get string value of convar with type '"~T.stringof~"'");
2449 protected override ulong getIntValue () /*nothrow @nogc*/ {
2450 static if (is(T : ulong) || is(T : double)) return cast(ulong)(getv()); else return ulong.init;
2453 protected override double getDoubleValue () /*nothrow @nogc*/ {
2454 static if (is(T : double) || is(T : ulong)) return cast(double)(getv()); else return double.init;
2457 private template PutVMx(string val) {
2458 static if (useAtomic) {
2459 enum PutVMx = "{ import core.atomic; if (cvSetter !is null) cvSetter(this, "~val~"); else atomicStore(*vptr, "~val~"); }";
2460 } else {
2461 enum PutVMx = "{ if (cvSetter !is null) cvSetter(this, "~val~"); else *vptr = "~val~"; }";
2465 protected override void setIntValue (ulong v, bool signed) /*nothrow @nogc*/ {
2466 import core.atomic;
2467 static if (is(XUQQ!T == enum)) {
2468 foreach (string mname; __traits(allMembers, TT)) {
2469 if (__traits(getMember, TT, mname) == v) {
2470 mixin(PutVMx!"cast(T)v");
2471 return;
2474 // alas
2475 conwriteln("invalid enum value '", v, "' for variable '", name, "'");
2476 } else static if (is(T : ulong) || is(T : double)) {
2477 mixin(PutVMx!"cast(T)v");
2478 } else static if (is(T : ConString)) {
2479 import core.stdc.stdio : snprintf;
2480 auto len = snprintf(tvbuf.ptr, tvbuf.length, (signed ? "%lld" : "%llu"), v);
2481 static if (is(T == string)) mixin(PutVMx!"cast(string)(tvbuf[0..len])"); // not really safe, but...
2482 else static if (is(T == ConString)) mixin(PutVMx!"cast(ConString)(tvbuf[0..len])");
2483 else static if (is(T == char[])) mixin(PutVMx!"tvbuf[0..len]");
2487 protected override void setDoubleValue (double v) /*nothrow @nogc*/ {
2488 import core.atomic;
2489 static if (is(XUQQ!T == enum)) {
2490 foreach (string mname; __traits(allMembers, TT)) {
2491 if (__traits(getMember, TT, mname) == v) {
2492 mixin(PutVMx!"cast(T)v");
2493 return;
2496 // alas
2497 conwriteln("invalid enum value '", v, "' for variable '", name, "'");
2498 } else static if (is(T : ulong) || is(T : double)) {
2499 mixin(PutVMx!"cast(T)v");
2500 } else static if (is(T : ConString)) {
2501 import core.stdc.stdio : snprintf;
2502 auto len = snprintf(tvbuf.ptr, tvbuf.length, "%g", v);
2503 static if (is(T == string)) mixin(PutVMx!"cast(string)(tvbuf[0..len])"); // not really safe, but...
2504 else static if (is(T == ConString)) mixin(PutVMx!"cast(ConString)(tvbuf[0..len])");
2505 else static if (is(T == char[])) mixin(PutVMx!"tvbuf[0..len]");
2509 protected override void setStrValue (string v) /*nothrow*/ {
2510 import core.atomic;
2511 static if (is(XUQQ!T == enum)) {
2512 try {
2513 ConString ss = v;
2514 auto vv = ConCommand.parseType!T(ss);
2515 mixin(PutVMx!"cast(T)vv");
2516 } catch (Exception) {
2517 conwriteln("invalid enum value '", v, "' for variable '", name, "'");
2519 } else static if (is(T == string) || is(T == ConString)) {
2520 mixin(PutVMx!"cast(T)v");
2521 } else static if (is(T == char[])) {
2522 mixin(PutVMx!"v.dup");
2526 protected override void setCCharValue (ConString v) /*nothrow*/ {
2527 import core.atomic;
2528 static if (is(XUQQ!T == enum)) {
2529 try {
2530 ConString ss = v;
2531 auto vv = ConCommand.parseType!T(ss);
2532 mixin(PutVMx!"cast(T)vv");
2533 } catch (Exception) {
2534 conwriteln("invalid enum value '", v, "' for variable '", name, "'");
2537 else static if (is(T == string)) mixin(PutVMx!"v.idup");
2538 else static if (is(T == ConString)) mixin(PutVMx!"v");
2539 else static if (is(T == char[])) mixin(PutVMx!"v.dup");
2542 override void printValue () {
2543 static if (is(T == enum)) {
2544 auto vx = getv();
2545 foreach (string mname; __traits(allMembers, TT)) {
2546 if (__traits(getMember, TT, mname) == vx) {
2547 conwrite(name);
2548 conwrite(" ");
2549 //writeQuotedString(mname);
2550 conwrite(mname);
2551 conwrite("\n");
2552 return;
2555 //FIXME: doubles?
2556 conwriteln(name, " ", cast(ulong)getv());
2557 } else static if (is(T : ConString)) {
2558 conwrite(name);
2559 conwrite(" ");
2560 writeQuotedString(getv());
2561 conwrite("\n");
2562 } else static if (is(XUQQ!T == char)) {
2563 conwrite(name);
2564 conwrite(" ");
2565 char[1] st = getv();
2566 if (st[0] <= ' ' || st[0] == 127 || st[0] == '"' || st[0] == '\\') {
2567 writeQuotedString(st[]);
2568 } else {
2569 conwrite(`"`);
2570 conwrite(st[]);
2571 conwrite(`"`);
2573 conwrite("\n");
2574 } else static if (is(T == bool)) {
2575 conwriteln(name, " ", (getv() ? "tan" : "ona"));
2576 } else {
2577 conwriteln(name, " ", strval);
2584 version(contest_vars) unittest {
2585 __gshared int vi = 42;
2586 __gshared string vs = "str";
2587 __gshared bool vb = true;
2588 auto cvi = new ConVar!int(&vi, "vi", "integer variable");
2589 auto cvs = new ConVar!string(&vs, "vs", "string variable");
2590 auto cvb = new ConVar!bool(&vb, "vb", "bool variable");
2591 cvi.exec("?");
2592 cvs.exec("?");
2593 cvb.exec("?");
2594 cvi.exec("");
2595 cvs.exec("");
2596 cvb.exec("");
2597 cvi.exec("666");
2598 cvs.exec("'fo\to'");
2599 cvi.exec("");
2600 cvs.exec("");
2601 conwriteln("vi=", vi);
2602 conwriteln("vs=[", vs, "]");
2603 cvs.exec("'?'");
2604 cvs.exec("");
2605 cvb.exec("tan");
2606 cvb.exec("");
2607 cvb.exec("ona");
2608 cvb.exec("");
2612 // don't use std.algo
2613 private void heapsort(alias lessfn, T) (T[] arr) {
2614 auto count = arr.length;
2615 if (count < 2) return; // nothing to do
2617 void siftDown(T) (T start, T end) {
2618 auto root = start;
2619 for (;;) {
2620 auto child = 2*root+1; // left child
2621 if (child > end) break;
2622 auto swap = root;
2623 if (lessfn(arr.ptr[swap], arr.ptr[child])) swap = child;
2624 if (child+1 <= end && lessfn(arr.ptr[swap], arr.ptr[child+1])) swap = child+1;
2625 if (swap == root) break;
2626 auto tmp = arr.ptr[swap];
2627 arr.ptr[swap] = arr.ptr[root];
2628 arr.ptr[root] = tmp;
2629 root = swap;
2633 auto end = count-1;
2634 // heapify
2635 auto start = (end-1)/2; // parent; always safe, as our array has at least two items
2636 for (;;) {
2637 siftDown(start, end);
2638 if (start-- == 0) break; // as `start` cannot be negative, use this condition
2641 while (end > 0) {
2643 auto tmp = arr.ptr[0];
2644 arr.ptr[0] = arr.ptr[end];
2645 arr.ptr[end] = tmp;
2647 --end;
2648 siftDown(0, end);
2653 private void conUnsafeArrayAppend(T) (ref T[] arr, auto ref T v) /*nothrow*/ {
2654 if (arr.length >= int.max/2) assert(0, "too many elements in array");
2655 auto optr = arr.ptr;
2656 arr ~= v;
2657 if (arr.ptr !is optr) {
2658 import core.memory : GC;
2659 optr = arr.ptr;
2660 if (optr is GC.addrOf(optr)) GC.setAttr(optr, GC.BlkAttr.NO_INTERIOR);
2665 private void addName (string name) {
2666 //{ import core.stdc.stdio; printf("%.*s\n", cast(uint)name.length, name.ptr); }
2667 // ascii only
2668 static bool strLessCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
2669 auto slen = s0.length;
2670 if (slen > s1.length) slen = s1.length;
2671 char c1;
2672 foreach (immutable idx, char c0; s0[0..slen]) {
2673 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower()
2674 c1 = s1.ptr[idx];
2675 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower()
2676 if (c0 < c1) return true;
2677 if (c0 > c1) return false;
2679 if (s0.length < s1.length) return true;
2680 if (s0.length > s1.length) return false;
2681 return false;
2684 if (name.length == 0) return;
2685 if (name !in cmdlist) {
2686 //cmdlistSorted ~= name;
2687 cmdlistSorted.conUnsafeArrayAppend(name);
2688 heapsort!strLessCI(cmdlistSorted);
2693 // ////////////////////////////////////////////////////////////////////////// //
2694 import std.range;
2696 void concmdRangeCompleter(IR, bool casesens=true) (ConCommand self, auto ref IR rng) if (isForwardRange!IR && is(ElementEncodingType!IR : const(char)[])) {
2697 if (rng.empty) return; // oops
2698 auto cs = conInputBuffer[0..conInputBufferCurX];
2699 ConCommand.getWord(cs); // skip command
2700 while (cs.length && cs[0] <= ' ') cs = cs[1..$];
2701 if (cs.length == 0) {
2702 conwriteln(self.name, ":");
2703 foreach (const(char)[] mname; rng) conwriteln(" ", mname);
2704 } else {
2705 if (cs[0] == '"' || cs[0] == '\'') return; // alas
2706 ConString pfx = ConCommand.getWord(cs);
2707 while (cs.length && cs[0] <= ' ') cs = cs[1..$];
2708 if (cs.length) return; // alas
2709 const(char)[] bestPfx;
2710 int count = 0;
2711 foreach (const(char)[] mname; rng.save) {
2712 if (mname.length >= pfx.length && strEquCI(mname[0..pfx.length], pfx)) {
2713 if (count == 0) {
2714 bestPfx = mname;
2715 } else {
2716 //if (mname.length < bestPfx.length) bestPfx = bestPfx[0..mname.length];
2717 usize pos = 0;
2718 while (pos < bestPfx.length && pos < mname.length) {
2719 char c0 = bestPfx[pos];
2720 char c1 = mname[pos];
2721 static if (!casesens) {
2722 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower()
2723 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower()
2725 if (c0 != c1) break;
2726 ++pos;
2728 if (pos < bestPfx.length) bestPfx = bestPfx[0..pos];
2730 ++count;
2733 if (count == 0 || bestPfx.length < pfx.length) { conwriteln(self.name, ": ???"); return; }
2734 foreach (char ch; bestPfx[pfx.length..$]) conAddInputChar(ch);
2735 if (count == 1) {
2736 conAddInputChar(' ');
2737 } else {
2738 conwriteln(self.name, ":");
2739 foreach (const(char)[] mname; rng) {
2740 if (mname.length >= bestPfx.length && mname[0..bestPfx.length] == bestPfx) {
2741 conwriteln(" ", mname);
2749 // ////////////////////////////////////////////////////////////////////////// //
2750 /// helper function for [cmdconSetFileCompleter]
2751 public string[] cmdconFilesList (string pfx, scope bool delegate (ConString name) nothrow isGood=null) nothrow {
2752 static bool startsWith (const(char)[] str, const(char)[] prefix) pure nothrow @trusted @nogc {
2753 if (prefix.length == 0) return true;
2754 if (prefix.length > str.length) return false;
2755 return (str[0..prefix.length] == prefix);
2758 import std.file;
2759 import std.path;
2760 string[] res;
2761 string path;
2762 if (pfx.length == 0) {
2763 path = ".";
2764 } else {
2765 if (pfx[$-1] == '/') {
2766 path = pfx;
2767 pfx = null;
2768 } else {
2769 path = pfx.dirName;
2770 pfx = pfx.baseName;
2773 string xxname (string fn, bool asDir=false) {
2774 if (fn == "/") return fn;
2775 if (asDir) {
2776 if (fn == ".") return "./";
2778 if (startsWith(fn, "./")) fn = fn[2..$];
2779 if (asDir) fn ~= "/";
2780 return fn;
2782 try {
2783 foreach (DirEntry de; dirEntries(path, SpanMode.shallow)) {
2784 if (de.baseName.length == 0) continue;
2785 if (de.baseName[0] == '.') continue;
2786 try {
2787 if (de.isFile) {
2788 if (isGood is null || isGood(de.name)) {
2789 if (pfx.length == 0 || startsWith(de.baseName, pfx)) res ~= xxname(de.name);
2791 } else if (de.isDir) {
2792 if (pfx.length == 0 || startsWith(de.baseName, pfx)) res ~= xxname(de.name, true);
2794 } catch (Exception e) {} // sorry
2796 } catch (Exception e) {} // sorry
2797 import std.algorithm : sort;
2798 res.sort!((string a, string b) {
2799 if (a[$-1] == '/') {
2800 if (b[$-1] != '/') return true;
2801 return (a < b);
2803 if (b[$-1] == '/') return false;
2804 return (a < b);
2806 return res;
2810 /// if `getCurrentFile` is not null, and sole "!" is given as argument, current file will be put
2811 public void cmdconSetFileCompleter (ConCommand cmd, string delegate () nothrow getCurrentFile=null, bool delegate (ConString name) nothrow isGood=null) {
2812 if (cmd is null) return;
2813 cmd.completer = delegate (ConCommand self) {
2814 auto cs = conInputBuffer[0..conInputBufferCurX];
2815 ConCommand.getWord(cs); // skip command
2816 while (cs.length && cs[0] <= ' ') cs = cs[1..$];
2817 // put currently loaded file to buffer
2818 if (cs == "!" && getCurrentFile !is null) {
2819 string cfn = getCurrentFile();
2820 if (cfn.length) {
2821 conAddInputChar(ConInputChar.Backspace); // remove "!"
2822 foreach (char ch; cfn) conAddInputChar(ch);
2823 return;
2826 string[] list = cmdconFilesList(cs.idup, isGood);
2827 if (list.length == 0) {
2828 conwriteln(self.name, ": no matching files");
2829 return;
2831 auto lc = conInputLastChange();
2832 self.concmdRangeCompleter(list);
2833 // if we ended with "/ ", remove space
2834 if (lc != conInputLastChange()) {
2835 cs = conInputBuffer[0..conInputBufferCurX];
2836 if (cs.length > 2 && cs[$-1] == ' ' && cs[$-2] == '/') {
2837 conAddInputChar(ConInputChar.Backspace);
2844 // ////////////////////////////////////////////////////////////////////////// //
2845 private void enumComplete(T) (ConCommand self) if (is(T == enum)) {
2846 auto cs = conInputBuffer[0..conInputBufferCurX];
2847 ConCommand.getWord(cs); // skip command
2848 while (cs.length && cs[0] <= ' ') cs = cs[1..$];
2849 if (cs.length == 0) {
2850 conwriteln(self.name, ":");
2851 foreach (string mname; __traits(allMembers, T)) conwriteln(" ", mname);
2852 } else {
2853 if (cs[0] == '"' || cs[0] == '\'') return; // alas
2854 ConString pfx = ConCommand.getWord(cs);
2855 while (cs.length && cs[0] <= ' ') cs = cs[1..$];
2856 if (cs.length) return; // alas
2857 string bestPfx;
2858 int count = 0;
2859 foreach (string mname; __traits(allMembers, T)) {
2860 if (mname.length >= pfx.length && strEquCI(mname[0..pfx.length], pfx)) {
2861 if (count == 0) {
2862 bestPfx = mname;
2863 } else {
2864 //if (mname.length < bestPfx.length) bestPfx = bestPfx[0..mname.length];
2865 usize pos = 0;
2866 while (pos < bestPfx.length && pos < mname.length) {
2867 char c0 = bestPfx[pos];
2868 char c1 = mname[pos];
2869 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower()
2870 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower()
2871 if (c0 != c1) break;
2872 ++pos;
2874 if (pos < bestPfx.length) bestPfx = bestPfx[0..pos];
2876 ++count;
2879 if (count == 0 || bestPfx.length < pfx.length) { conwriteln(self.name, ": ???"); return; }
2880 foreach (char ch; bestPfx[pfx.length..$]) conAddInputChar(ch);
2881 if (count == 1) {
2882 conAddInputChar(' ');
2883 } else {
2884 conwriteln(self.name, ":");
2885 foreach (string mname; __traits(allMembers, T)) {
2886 if (mname.length >= bestPfx.length && mname[0..bestPfx.length] == bestPfx) {
2887 conwriteln(" ", mname);
2895 private void boolComplete(T) (ConCommand self) if (is(T == bool)) {
2896 auto cs = conInputBuffer[0..conInputBufferCurX];
2897 ConCommand.getWord(cs); // skip command
2898 while (cs.length && cs[0] <= ' ') cs = cs[1..$];
2899 if (cs.length > 0) {
2900 enum Cmd = "toggle";
2901 foreach (immutable idx, char ch; Cmd) {
2902 if (idx >= cs.length) break;
2903 if (cs[idx] != ch) return; // alas
2905 if (cs.length > Cmd.length) return;
2906 foreach (char ch; Cmd[cs.length..$]) conAddInputChar(ch);
2907 conAddInputChar(' ');
2908 } else {
2909 conwriteln(" toggle?");
2914 /** register console variable with getter and setter.
2916 * Params:
2917 * aname = variable name
2918 * ahelp = help text
2919 * dgg = getter
2920 * dgs = setter
2921 * attrs = convar attributes (see `ConVarAttr`)
2923 public ConVarBase conRegVar(T) (string aname, string ahelp, T delegate (ConVarBase self) dgg, void delegate (ConVarBase self, T nv) dgs, const(ConVarAttr)[] attrs...)
2924 if (!is(T == struct) && !is(T == class))
2926 if (dgg is null || dgs is null) assert(0, "cmdcon: both getter and setter should be defined for convar");
2927 if (aname.length > 0) {
2928 addName(aname);
2929 auto cv = new ConVar!T(null, aname, ahelp);
2930 cv.setAttrs(attrs);
2931 cv.cvGetter = dgg;
2932 cv.cvSetter = dgs;
2933 cmdlist[aname] = cv;
2934 return cv;
2935 } else {
2936 return null;
2941 private enum RegVarMixin(string cvcreate) =
2942 `static assert(isGoodVar!v, "console variable '"~v.stringof~"' must be shared or __gshared");`~
2943 `if (aname.length == 0) {`~
2944 `aname = (&v).stringof[2..$]; /*HACK*/`~
2945 ` foreach_reverse (immutable cidx, char ch; aname) if (ch == '.') { aname = aname[cidx+1..$]; break; }`~
2946 `}`~
2947 `if (aname.length > 0) {`~
2948 ` addName(aname);`~
2949 ` auto cv = `~cvcreate~`;`~
2950 ` cv.setAttrs(attrs);`~
2951 ` alias UT = XUQQ!(typeof(v));`~
2952 ` static if (is(UT == enum)) {`~
2953 ` import std.functional : toDelegate;`~
2954 ` cv.argcomplete = toDelegate(&enumComplete!UT);`~
2955 ` } else static if (is(UT == bool)) {`~
2956 ` import std.functional : toDelegate;`~
2957 ` cv.argcomplete = toDelegate(&boolComplete!UT);`~
2958 ` }`~
2959 ` cmdlist[aname] = cv;`~
2960 ` return cv;`~
2961 `} else {`~
2962 ` return null;`~
2963 `}`;
2966 /** register integral console variable with bounded value.
2968 * Params:
2969 * v = variable symbol
2970 * aminv = minimum value
2971 * amaxv = maximum value
2972 * aname = variable name
2973 * ahelp = help text
2974 * attrs = convar attributes (see `ConVarAttr`)
2976 public ConVarBase conRegVar(alias v, T) (T aminv, T amaxv, string aname, string ahelp, const(ConVarAttr)[] attrs...)
2977 if ((isIntegral!(typeof(v)) && isIntegral!T) || (isFloatingPoint!(typeof(v)) && (isIntegral!T || isFloatingPoint!T)))
2979 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp)`);
2982 /** register integral console variable with old/new delegate.
2984 * Params:
2985 * v = variable symbol
2986 * aname = variable name
2987 * ahelp = help text
2988 * cdg = old/new change delegate
2989 * attrs = convar attributes (see `ConVarAttr`)
2991 ConVarBase conRegVar(alias v) (string aname, string ahelp, void delegate (ConVarBase self, XUQQ!(typeof(v)) oldv, XUQQ!(typeof(v)) newv) cdg, const(ConVarAttr)[] attrs...)
2992 if (is(XUQQ!(typeof(v)) : long) || is(XUQQ!(typeof(v)) : double) || is(XUQQ!(typeof(v)) : ConString) || is(XUQQ!(typeof(v)) == bool) || is(XUQQ!(typeof(v)) == enum)) {
2993 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookAfterChangeVV = cdg`);
2996 /** register integral console variable with bounded value and old/new delegate.
2998 * Params:
2999 * v = variable symbol
3000 * aminv = minimum value
3001 * amaxv = maximum value
3002 * aname = variable name
3003 * ahelp = help text
3004 * cdg = old/new change delegate
3005 * attrs = convar attributes (see `ConVarAttr`)
3007 ConVarBase conRegVar(alias v) (XUQQ!(typeof(v)) amin, XUQQ!(typeof(v)) amax, string aname, string ahelp, void delegate (ConVarBase self, XUQQ!(typeof(v)) oldv, XUQQ!(typeof(v)) newv) cdg, const(ConVarAttr)[] attrs...)
3008 if (is(XUQQ!(typeof(v)) : long) || is(XUQQ!(typeof(v)) : double) && !is(XUQQ!(typeof(v)) == bool) && !is(XUQQ!(typeof(v)) == enum)) {
3009 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, amin, amax, aname, ahelp); cv.hookAfterChangeVV = cdg`);
3012 /** register integral console variable with bounded value.
3014 * Params:
3015 * v = variable symbol
3016 * aminv = minimum value
3017 * amaxv = maximum value
3018 * aname = variable name
3019 * ahelp = help text
3020 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3021 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3022 * attrs = convar attributes (see `ConVarAttr`)
3024 public ConVarBase conRegVar(alias v, T) (T aminv, T amaxv, string aname, string ahelp, ConVarBase.PreChangeHookCB bcb, ConVarBase.PostChangeHookCB acb, const(ConVarAttr)[] attrs...) if (isIntegral!(typeof(v)) && isIntegral!T) {
3025 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3028 /** register integral console variable with bounded value.
3030 * Params:
3031 * v = variable symbol
3032 * aminv = minimum value
3033 * amaxv = maximum value
3034 * aname = variable name
3035 * ahelp = help text
3036 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3037 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3038 * attrs = convar attributes (see `ConVarAttr`)
3040 public ConVarBase conRegVar(alias v, T) (T aminv, T amaxv, string aname, string ahelp, ConVarBase.PostChangeHookCB acb, ConVarBase.PreChangeHookCB bcb, const(ConVarAttr)[] attrs...) if (isIntegral!(typeof(v)) && isIntegral!T) {
3041 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3044 /** register integral console variable with bounded value.
3046 * Params:
3047 * v = variable symbol
3048 * aminv = minimum value
3049 * amaxv = maximum value
3050 * aname = variable name
3051 * ahelp = help text
3052 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3053 * attrs = convar attributes (see `ConVarAttr`)
3055 public ConVarBase conRegVar(alias v, T) (T aminv, T amaxv, string aname, string ahelp, ConVarBase.PreChangeHookCB bcb, const(ConVarAttr)[] attrs...) if (isIntegral!(typeof(v)) && isIntegral!T) {
3056 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookBeforeChange = bcb`);
3059 /** register integral console variable with bounded value.
3061 * Params:
3062 * v = variable symbol
3063 * aminv = minimum value
3064 * amaxv = maximum value
3065 * aname = variable name
3066 * ahelp = help text
3067 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3068 * attrs = convar attributes (see `ConVarAttr`)
3070 public ConVarBase conRegVar(alias v, T) (T aminv, T amaxv, string aname, string ahelp, ConVarBase.PostChangeHookCB acb, const(ConVarAttr)[] attrs...) if (isIntegral!(typeof(v)) && isIntegral!T) {
3071 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, cast(typeof(v))aminv, cast(typeof(v))amaxv, aname, ahelp); cv.hookAfterChange = acb`);
3075 /** register console variable.
3077 * Params:
3078 * v = variable symbol
3079 * aname = variable name
3080 * ahelp = help text
3081 * attrs = convar attributes (see `ConVarAttr`)
3083 public ConVarBase conRegVar(alias v) (string aname, string ahelp, const(ConVarAttr)[] attrs...) if (!isCallable!(typeof(v))) {
3084 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, aname, ahelp)`);
3087 /** register console variable.
3089 * Params:
3090 * v = variable symbol
3091 * aname = variable name
3092 * ahelp = help text
3093 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3094 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3095 * attrs = convar attributes (see `ConVarAttr`)
3097 public ConVarBase conRegVar(alias v) (string aname, string ahelp, ConVarBase.PreChangeHookCB bcb, ConVarBase.PostChangeHookCB acb, const(ConVarAttr)[] attrs...) if (!isCallable!(typeof(v))) {
3098 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3101 /** register console variable.
3103 * Params:
3104 * v = variable symbol
3105 * aname = variable name
3106 * ahelp = help text
3107 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3108 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3109 * attrs = convar attributes (see `ConVarAttr`)
3111 public ConVarBase conRegVar(alias v) (string aname, string ahelp, ConVarBase.PostChangeHookCB acb, ConVarBase.PreChangeHookCB bcb, const(ConVarAttr)[] attrs...) if (!isCallable!(typeof(v))) {
3112 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookBeforeChange = bcb; cv.hookAfterChange = acb`);
3115 /** register console variable.
3117 * Params:
3118 * v = variable symbol
3119 * aname = variable name
3120 * ahelp = help text
3121 * bcb = "before value change" hook: `bool (ConVarBase self, ConString valstr)`, return `false` to block change
3122 * attrs = convar attributes (see `ConVarAttr`)
3124 public ConVarBase conRegVar(alias v) (string aname, string ahelp, ConVarBase.PreChangeHookCB bcb, const(ConVarAttr)[] attrs...) if (!isCallable!(typeof(v))) {
3125 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookBeforeChange = bcb`);
3128 /** register console variable.
3130 * Params:
3131 * v = variable symbol
3132 * aname = variable name
3133 * ahelp = help text
3134 * acb = "after value change" hook: `(ConVarBase self, ConString valstr)`
3135 * attrs = convar attributes (see `ConVarAttr`)
3137 public ConVarBase conRegVar(alias v) (string aname, string ahelp, ConVarBase.PostChangeHookCB acb, const(ConVarAttr)[] attrs...) if (!isCallable!(typeof(v))) {
3138 mixin(RegVarMixin!`new ConVar!(typeof(v))(&v, aname, ahelp); cv.hookAfterChange = acb`);
3142 // ////////////////////////////////////////////////////////////////////////// //
3143 // delegate
3144 public class ConFuncBase : ConCommand {
3145 this (string aname, string ahelp=null) { super(aname, ahelp); }
3149 /// use `conRegFunc!((ConFuncVA va) {...}, ...)` to switch off automatic argument parsing
3150 public struct ConFuncVA {
3151 ConString cmdline;
3155 /** register console command.
3157 * Params:
3158 * fn = symbol
3159 * aname = variable name
3160 * ahelp = help text
3162 public ConFuncBase conRegFunc(alias fn) (string aname, string ahelp) if (isCallable!fn) {
3163 // we have to make the class nested, so we can use `dg`, which keeps default args
3165 // hack for inline lambdas
3166 static if (is(typeof(&fn))) {
3167 auto dg = &fn;
3168 } else {
3169 auto dg = fn;
3172 class ConFunc : ConFuncBase {
3173 this (string aname, string ahelp=null) { super(aname, ahelp); }
3175 override void exec (ConString cmdline) {
3176 if (checkHelp(cmdline)) { showHelp; return; }
3177 Parameters!dg args;
3178 static if (args.length == 0) {
3179 if (hasArgs(cmdline)) {
3180 conwriteln("too many args for command '", name, "'");
3181 } else {
3182 dg();
3184 } else static if (args.length == 1 && is(typeof(args[0]) == ConFuncVA)) {
3185 args[0].cmdline = cmdline;
3186 dg(args);
3187 } else {
3188 alias defaultArguments = ParameterDefaults!fn;
3189 //pragma(msg, "defs: ", defaultArguments);
3190 import std.conv : to;
3191 ConString[128] rest; // to avoid allocations in most cases
3192 ConString[] restdyn; // will be used if necessary
3193 uint restpos;
3194 foreach (/*auto*/ idx, ref arg; args) {
3195 // populate arguments, with user data if available,
3196 // default if not, and throw if no argument provided
3197 if (hasArgs(cmdline)) {
3198 import std.conv : ConvException;
3199 static if (is(typeof(arg) == ConString[])) {
3200 auto xidx = idx+1;
3201 if (idx != args.length-1) {
3202 conwriteln("error parsing argument #", xidx, " for command '", name, "'");
3203 return;
3205 while (hasArgs(cmdline)) {
3206 try {
3207 ConString cs = parseType!ConString(cmdline);
3208 if (restpos == rest.length) { restdyn = rest[]; ++restpos; }
3209 if (restpos < rest.length) rest[restpos++] = cs; else restdyn ~= cs;
3210 ++xidx;
3211 } catch (ConvException) {
3212 conwriteln("error parsing argument #", xidx, " for command '", name, "'");
3213 return;
3216 arg = (restpos <= rest.length ? rest[0..restpos] : restdyn);
3217 } else {
3218 try {
3219 arg = parseType!(typeof(arg))(cmdline);
3220 } catch (ConvException) {
3221 conwriteln("error parsing argument #", idx+1, " for command '", name, "'");
3222 return;
3225 } else {
3226 static if (!is(defaultArguments[idx] == void)) {
3227 arg = defaultArguments[idx];
3228 } else {
3229 conwriteln("required argument #", idx+1, " for command '", name, "' is missing");
3230 return;
3234 if (hasArgs(cmdline)) {
3235 conwriteln("too many args for command '", name, "'");
3236 return;
3238 //static if (is(ReturnType!dg == void))
3239 dg(args);
3244 static if (is(typeof(&fn))) {
3245 if (aname.length == 0) aname = (&fn).stringof[2..$]; // HACK
3247 if (aname.length > 0) {
3248 auto cf = new ConFunc(aname, ahelp);
3249 addName(aname);
3250 cmdlist[aname] = cf;
3251 return cf;
3252 } else {
3253 return null;
3258 // ////////////////////////////////////////////////////////////////////////// //
3259 __gshared /*ConCommand*/Object[string] cmdlist; // ConCommand or ConAlias
3260 __gshared string[] cmdlistSorted;
3263 /** set argument completion delegate for command.
3265 * delegate will be called from `conAddInputChar()`.
3266 * delegate can use `conInputBuffer()` to get current input buffer,
3267 * `conInputBufferCurX()` to get cursor position, and
3268 * `conAddInputChar()` itself to put new chars into buffer.
3270 void conSetArgCompleter (ConString cmdname, ConCommand.ArgCompleteCB ac) {
3271 if (auto cp = cmdname in cmdlist) {
3272 if (auto cmd = cast(ConCommand)(*cp)) cmd.argcomplete = ac;
3277 // ////////////////////////////////////////////////////////////////////////// //
3278 // all following API is thread-unsafe, if the opposite is not written
3279 public bool conHasCommand (ConString name) { pragma(inline, true); return ((name in cmdlist) !is null); } /// check if console has a command with a given name (thread-unsafe)
3280 public bool conHasAlias (ConString name) { if (auto cc = name in cmdlist) return (cast(ConAlias)(*cc) !is null); else return false; } /// check if console has an alias with a given name (thread-unsafe)
3281 public bool conHasVar (ConString name) { if (auto cc = name in cmdlist) return (cast(ConVarBase)(*cc) !is null); else return false; } /// check if console has a variable with a given name (thread-unsafe)
3283 /// known console commands (funcs and vars) range (thread-unsafe)
3284 /// type: "all", "vars", "funcs"
3285 public auto conByCommand(string type="all") () if (type == "all" || type == "vars" || type == "funcs" || type == "aliases" || type == "archive") {
3286 static struct Range(string type) {
3287 private:
3288 usize idx;
3290 private:
3291 this (usize stidx) {
3292 static if (type == "all") {
3293 idx = stidx;
3294 } else static if (type == "vars") {
3295 while (stidx < cmdlistSorted.length && (cast(ConVarBase)cmdlist[cmdlistSorted.ptr[stidx]]) is null) ++stidx;
3296 idx = stidx;
3297 } else static if (type == "funcs") {
3298 while (stidx < cmdlistSorted.length && (cast(ConFuncBase)cmdlist[cmdlistSorted.ptr[stidx]]) is null) ++stidx;
3299 idx = stidx;
3300 } else static if (type == "aliases") {
3301 while (stidx < cmdlistSorted.length && (cast(ConAlias)cmdlist[cmdlistSorted.ptr[stidx]]) is null) ++stidx;
3302 idx = stidx;
3303 } else static if (type == "archive") {
3304 while (stidx < cmdlistSorted.length) {
3305 if (auto v = cast(ConVarBase)cmdlist[cmdlistSorted.ptr[stidx]]) {
3306 if (v.attrArchive) break;
3308 ++stidx;
3310 idx = stidx;
3311 } else {
3312 static assert(0, "wtf?!");
3316 public:
3317 @property bool empty() () { pragma(inline, true); return (idx >= cmdlistSorted.length); }
3318 @property string front() () { pragma(inline, true); return (idx < cmdlistSorted.length ? cmdlistSorted.ptr[idx] : null); }
3319 @property bool frontIsVar() () { pragma(inline, true); return (idx < cmdlistSorted.length ? (cast(ConVarBase)cmdlist[cmdlistSorted.ptr[idx]] !is null) : false); }
3320 @property bool frontIsFunc() () { pragma(inline, true); return (idx < cmdlistSorted.length ? (cast(ConFuncBase)cmdlist[cmdlistSorted.ptr[idx]] !is null) : false); }
3321 @property bool frontIsAlias() () { pragma(inline, true); return (idx < cmdlistSorted.length ? (cast(ConAlias)cmdlist[cmdlistSorted.ptr[idx]] !is null) : false); }
3322 void popFront () {
3323 if (idx >= cmdlistSorted.length) return;
3324 static if (type == "all") {
3325 //pragma(inline, true);
3326 ++idx;
3327 } else static if (type == "vars") {
3328 ++idx;
3329 while (idx < cmdlistSorted.length && (cast(ConVarBase)cmdlist[cmdlistSorted.ptr[idx]]) is null) ++idx;
3330 } else static if (type == "funcs") {
3331 ++idx;
3332 while (idx < cmdlistSorted.length && (cast(ConFuncBase)cmdlist[cmdlistSorted.ptr[idx]]) is null) ++idx;
3333 } else static if (type == "aliases") {
3334 ++idx;
3335 while (idx < cmdlistSorted.length && (cast(ConAlias)cmdlist[cmdlistSorted.ptr[idx]]) is null) ++idx;
3336 } else static if (type == "archive") {
3337 ++idx;
3338 while (idx < cmdlistSorted.length) {
3339 if (auto v = cast(ConVarBase)cmdlist[cmdlistSorted.ptr[idx]]) {
3340 if (v.attrArchive) break;
3342 ++idx;
3344 } else {
3345 static assert(0, "wtf?!");
3349 return Range!type(0);
3353 // ////////////////////////////////////////////////////////////////////////// //
3354 // thread-safe
3356 /// get console variable value (thread-safe)
3357 public T conGetVar(T=ConString) (ConString s) {
3358 consoleLock();
3359 scope(exit) consoleUnlock();
3360 if (auto cc = s in cmdlist) {
3361 if (auto cv = cast(ConVarBase)(*cc)) return cv.value!T;
3363 return T.init;
3366 /// ditto
3367 public T conGetVar(T=ConString) (ConVarBase v) {
3368 consoleLock();
3369 scope(exit) consoleUnlock();
3370 if (v !is null) return v.value!T;
3371 return T.init;
3375 /// set console variable value (thread-safe)
3376 public void conSetVar(T) (ConString s, T val) {
3377 consoleLock();
3378 scope(exit) consoleUnlock();
3379 if (auto cc = s in cmdlist) {
3380 if (auto cv = cast(ConVarBase)(*cc)) cv.value = val;
3384 /// ditto
3385 public void conSetVar(T) (ConVarBase v, T val) {
3386 consoleLock();
3387 scope(exit) consoleUnlock();
3388 if (v !is null) v.value = val;
3392 /// "seal" console variable (i.e. make it read-only) (thread-safe)
3393 public void conSealVar (ConString s) {
3394 consoleLock();
3395 scope(exit) consoleUnlock();
3396 if (auto cc = s in cmdlist) {
3397 if (auto cv = cast(ConVarBase)(*cc)) cv.attrReadOnly = true;
3401 /// ditto
3402 public void conSealVar (ConVarBase v) {
3403 consoleLock();
3404 scope(exit) consoleUnlock();
3405 if (v !is null) v.attrReadOnly = true;
3409 /// "seal" console variable (i.e. make it r/w) (thread-safe)
3410 public void conUnsealVar (ConString s) {
3411 consoleLock();
3412 scope(exit) consoleUnlock();
3413 if (auto cc = s in cmdlist) {
3414 if (auto cv = cast(ConVarBase)(*cc)) cv.attrReadOnly = false;
3418 /// ditto
3419 public void conUnsealVar (ConVarBase v) {
3420 consoleLock();
3421 scope(exit) consoleUnlock();
3422 if (v !is null) v.attrReadOnly = false;
3426 // ////////////////////////////////////////////////////////////////////////// //
3427 // thread-safe
3429 shared bool ccWaitPrepends = true;
3430 public @property bool conWaitPrepends () nothrow @trusted @nogc { pragma(inline, true); import core.atomic : atomicLoad; return atomicLoad(ccWaitPrepends); } /// shoud "wait" prepend rest of the alias?
3431 public @property void conWaitPrepends (bool v) nothrow @trusted @nogc { pragma(inline, true); import core.atomic : atomicStore; atomicStore(ccWaitPrepends, v); } /// ditto
3433 __gshared char* ccWaitPrependStr = null;
3434 __gshared uint ccWaitPrependStrSize, ccWaitPrependStrLen;
3436 ConString prependStr () { return ccWaitPrependStr[0..ccWaitPrependStrLen]; }
3438 void clearPrependStr () { ccWaitPrependStrLen = 0; return; }
3440 void setPrependStr (ConString s) {
3441 if (s.length == 0) { ccWaitPrependStrLen = 0; return; }
3442 if (s.length >= int.max/8) assert(0, "console command too long"); //FIXME
3443 if (s.length > ccWaitPrependStrSize) {
3444 import core.stdc.stdlib : realloc;
3445 ccWaitPrependStr = cast(char*)realloc(ccWaitPrependStr, s.length);
3446 if (ccWaitPrependStr is null) assert(0, "out of memory in cmdcon"); //FIXME
3447 ccWaitPrependStrSize = cast(uint)s.length;
3449 assert(s.length <= ccWaitPrependStrSize);
3450 ccWaitPrependStrLen = cast(uint)s.length;
3451 ccWaitPrependStr[0..s.length] = s[];
3455 /// execute console command (thread-safe)
3456 /// return `true` if `wait` pseudocommant was hit
3457 public bool conExecute (ConString s) {
3458 bool waitHit = false;
3459 auto ss = s; // anchor it
3460 consoleLock();
3461 scope(exit) consoleUnlock();
3463 enum MaxAliasExpand = 256*1024; // 256KB
3465 void conExecuteInternal (ConString s, int aliassize, int aliaslevel) {
3466 ConString anchor = s, curs = s;
3467 for (;;) {
3468 if (aliaslevel > 0) {
3469 s = conGetCommandStr(curs);
3470 if (s is null) return;
3471 } else {
3472 curs = null;
3474 auto w = ConCommand.getWord(s);
3475 if (w is null) return;
3476 // "wait" pseudocommand
3477 if (w == "wait") {
3478 waitHit = true;
3479 // insert the rest of the code into command queue (at the top),
3480 // so it will be processed on the next `conProcessQueue()` call
3481 while (curs.length) {
3482 while (curs.length && curs.ptr[0] <= ' ') curs = curs[1..$];
3483 if (curs.length == 0 || curs.ptr[0] != '#') break;
3484 // comment; skip it
3485 while (curs.length && curs.ptr[0] != '\n') curs = curs[1..$];
3487 if (curs.length) {
3488 // has something to insert
3489 if (conWaitPrepends) {
3490 //ccWaitPrependStr = curs;
3491 //concmdPrepend(curs);
3492 setPrependStr(curs);
3493 } else {
3494 concmdAdd!true(curs); // ensure new command
3497 return;
3499 version(iv_cmdcon_debug_wait) conwriteln("CMD: <", w, ">; args=<", s, ">; rest=<", curs, ">");
3500 if (auto cobj = w in cmdlist) {
3501 if (auto cmd = cast(ConCommand)(*cobj)) {
3502 // execute command
3503 while (s.length && s.ptr[0] <= 32) s = s[1..$];
3504 //conwriteln("'", s, "'");
3505 consoleUnlock();
3506 scope(exit) consoleLock();
3507 cmd.exec(s);
3508 } else if (auto ali = cast(ConAlias)(*cobj)) {
3509 // execute alias
3510 //TODO: alias arguments
3511 auto atext = ali.get;
3512 //conwriteln("ALIAS: '", w, "': '", atext, "'");
3513 if (atext.length) {
3514 if (aliassize+atext.length > MaxAliasExpand) throw ConAlias.exAliasTooDeep;
3515 if (aliaslevel >= 128) throw ConAlias.exAliasTooDeep;
3516 conExecuteInternal(atext, aliassize+cast(int)atext.length, aliaslevel+1);
3518 } else {
3519 conwrite("command ");
3520 ConCommand.writeQuotedString(w);
3521 conwrite(" is of unknown type (internal error)");
3522 conwrite("\n");
3524 } else {
3525 conwrite("command ");
3526 ConCommand.writeQuotedString(w);
3527 conwrite(" not found");
3528 conwrite("\n");
3530 s = curs;
3534 try {
3535 conExecuteInternal(s, 0, 0);
3536 } catch (Exception) {
3537 conwriteln("error executing console command:\n ", s);
3539 return waitHit;
3543 // ////////////////////////////////////////////////////////////////////////// //
3545 version(contest_func) unittest {
3546 static void xfunc (int v, int x=42) { conwriteln("xfunc: v=", v, "; x=", x); }
3548 conRegFunc!xfunc("", "function with two int args (last has default value '42')");
3549 conExecute("xfunc ?");
3550 conExecute("xfunc 666");
3551 conExecute("xfunc");
3553 conRegFunc!({conwriteln("!!!");})("bang", "dummy function");
3554 conExecute("bang");
3556 conRegFunc!((ConFuncVA va) {
3557 int idx = 1;
3558 for (;;) {
3559 auto w = ConCommand.getWord(va.cmdline);
3560 if (w is null) break;
3561 conwriteln("#", idx, ": [", w, "]");
3562 ++idx;
3564 })("doo", "another dummy function");
3565 conExecute("doo 1 2 ' 34 '");
3569 // ////////////////////////////////////////////////////////////////////////// //
3570 /** get console commad from array of text.
3572 * console commands are delimited with newlines, but can include various quoted chars and such.
3573 * this function will take care of that, and return something suitable for passing to `conExecute`.
3575 * it will return `null` if there is no command (i.e. end-of-text reached).
3577 public ConString conGetCommandStr (ref ConString s) {
3578 for (;;) {
3579 while (s.length > 0 && s[0] <= 32) s = s[1..$];
3580 if (s.length == 0) return null;
3581 if (s.ptr[0] != ';') break;
3582 s = s[1..$];
3585 usize pos = 0;
3587 void skipString () {
3588 char qch = s.ptr[pos++];
3589 while (pos < s.length) {
3590 if (s.ptr[pos] == qch) { ++pos; break; }
3591 if (s.ptr[pos++] == '\\') {
3592 if (pos < s.length) {
3593 if (s.ptr[pos] == 'x' || s.ptr[pos] == 'X') pos += 2; else ++pos;
3599 void skipLine () {
3600 while (pos < s.length) {
3601 if (s.ptr[pos] == '"' || s.ptr[pos] == '\'') {
3602 skipString();
3603 } else if (s.ptr[pos++] == '\n') {
3604 break;
3609 if (s.ptr[0] == '#') {
3610 skipLine();
3611 if (pos >= s.length) { s = s[$..$]; return null; }
3612 s = s[pos..$];
3613 pos = 0;
3616 while (pos < s.length) {
3617 if (s.ptr[pos] == '"' || s.ptr[pos] == '\'') {
3618 skipString();
3619 } else if (s.ptr[pos] == ';' || s.ptr[pos] == '#' || s.ptr[pos] == '\n') {
3620 auto res = s[0..pos];
3621 if (s.ptr[pos] == '#') s = s[pos..$]; else s = s[pos+1..$];
3622 return res;
3623 } else {
3624 ++pos;
3627 auto res = s[];
3628 s = s[$..$];
3629 return res;
3633 version(contest_cpx) unittest {
3634 ConString s = "boo; woo \";\" 42#cmt\ntest\nfoo";
3636 auto c = conGetCommandStr(s);
3637 conwriteln("[", c, "] : [", s, "]");
3640 auto c = conGetCommandStr(s);
3641 conwriteln("[", c, "] : [", s, "]");
3644 auto c = conGetCommandStr(s);
3645 conwriteln("[", c, "] : [", s, "]");
3648 auto c = conGetCommandStr(s);
3649 conwriteln("[", c, "] : [", s, "]");
3654 // ////////////////////////////////////////////////////////////////////////// //
3655 // console always has "echo" command, no need to register it.
3656 public class ConCommandEcho : ConCommand {
3657 this () { super("echo", "write string to console"); }
3659 override void exec (ConString cmdline) {
3660 if (checkHelp(cmdline)) { showHelp; return; }
3661 if (!hasArgs(cmdline)) { conwriteln; return; }
3662 bool needSpace = false;
3663 for (;;) {
3664 auto w = getWord(cmdline);
3665 if (w is null) break;
3666 if (needSpace) conwrite(" "); else needSpace = true;
3667 while (w.length) {
3668 usize pos = 0;
3669 while (pos < w.length && w.ptr[pos] != '$') ++pos;
3670 if (w.length-pos > 1 && w.ptr[pos+1] == '$') {
3671 conwrite(w[0..pos+1]);
3672 w = w[pos+2..$];
3673 } else if (w.length-pos <= 1) {
3674 conwrite(w);
3675 break;
3676 } else {
3677 // variable name
3678 ConString vname;
3679 if (pos > 0) conwrite(w[0..pos]);
3680 ++pos;
3681 if (w.ptr[pos] == '{') {
3682 w = w[pos+1..$];
3683 pos = 0;
3684 while (pos < w.length && w.ptr[pos] != '}') ++pos;
3685 vname = w[0..pos];
3686 if (pos < w.length) ++pos;
3687 w = w[pos..$];
3688 } else {
3689 w = w[pos..$];
3690 pos = 0;
3691 while (pos < w.length) {
3692 char ch = w.ptr[pos];
3693 if (ch == '_' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
3694 ++pos;
3695 } else {
3696 break;
3699 vname = w[0..pos];
3700 w = w[pos..$];
3702 if (vname.length) {
3703 if (auto cc = vname in cmdlist) {
3704 if (auto cv = cast(ConVarBase)(*cc)) {
3705 auto v = cv.strval;
3706 conwrite(v);
3707 } else {
3708 conwrite("${!");
3709 conwrite(vname);
3710 conwrite("}");
3712 } else {
3713 conwrite("${");
3714 conwrite(vname);
3715 conwrite("}");
3721 conwrite("\n");
3726 // ////////////////////////////////////////////////////////////////////////// //
3727 // console always has "alias" command, no need to register it.
3728 public class ConCommandAlias : ConCommand {
3729 this () { super("alias", "set or remove alias"); }
3731 override void exec (ConString cmdline) {
3732 if (checkHelp(cmdline)) { showHelp; return; }
3733 if (!hasArgs(cmdline)) { conwriteln("alias what?"); return; }
3735 auto name = getWord(cmdline);
3736 if (name is null || name.length == 0) { conwriteln("alias what?"); return; }
3737 if (name.length > 128) { conwriteln("alias name too long: ", name); return; }
3739 char[129] nbuf = void;
3740 nbuf[0..name.length] = name;
3741 const(char)[] xname = nbuf[0..name.length];
3743 // alias value
3744 auto v = getWord(cmdline);
3745 while (v.length && v[0] <= ' ') v = v[1..$];
3746 while (v.length && v[$-1] <= ' ') v = v[0..$-1];
3748 if (hasArgs(cmdline)) { conwriteln("too many arguments to `alias` command"); return; }
3750 if (auto cp = xname in cmdlist) {
3751 // existing alias?
3752 auto ali = cast(ConAlias)(*cp);
3753 if (ali is null) { conwriteln("can't change existing command/var to alias: ", xname); return; }
3754 if (v.length == 0) {
3755 // remove
3756 cmdlist.remove(cast(string)xname); // it is safe to cast here
3757 foreach (immutable idx, string n; cmdlistSorted) {
3758 if (n == xname) {
3759 foreach (immutable c; idx+1..cmdlistSorted.length) cmdlistSorted[c-1] = cmdlistSorted[c];
3760 break;
3763 } else {
3764 // change
3765 ali.set(v);
3767 return;
3768 } else {
3769 // new alias
3770 auto ali = new ConAlias(v);
3771 //conwriteln(xname, ": <", ali.get, ">");
3772 string sname = xname.idup;
3773 addName(sname);
3774 cmdlist[sname] = ali;
3780 // ////////////////////////////////////////////////////////////////////////// //
3781 /// create "user console variable"
3782 ConVarBase conRegUserVar(T) (string aname, string help, const(ConVarAttr)[] attrs...) {
3783 if (aname.length == 0) return null;
3784 ConVarBase v;
3785 static if (is(T : long) || is(T : double)) {
3786 T* var = new T;
3787 v = new ConVar!T(var, aname, help);
3788 } else static if (is(T : const(char)[])) {
3789 T[] var;
3790 var.length = 1;
3791 v = new ConVar!T(&var[0], aname, help);
3792 } else {
3793 static assert(0, "can't create uservar of type '"~T.stringof~"'");
3795 v.setAttrs(attrs);
3796 v.mAttrs |= ConVarAttr.User;
3797 addName(aname);
3798 cmdlist[aname] = v;
3799 return v;
3803 /// create bounded "user console variable"
3804 ConVarBase conRegUserVar(T) (T amin, T amax, string aname, string help, const(ConVarAttr)[] attrs...) if (is(T : long) || is(T : double)) {
3805 if (aname.length == 0) return null;
3806 ConVarBase v;
3807 static if (is(T : long) || is(T : double)) {
3808 T* var = new T;
3809 v = new ConVar!T(var, amin, amax, aname, help);
3810 } else static if (is(T : const(char)[])) {
3811 T[] var;
3812 var.length = 1;
3813 v = new ConVar!T(&var[0], aname, help);
3814 } else {
3815 static assert(0, "can't create uservar of type '"~T.stringof~"'");
3817 c.setAttrs(attrs);
3818 v.mAttrs |= ConVarAttr.User;
3819 addName(aname);
3820 cmdlist[aname] = v;
3821 return v;
3825 // ////////////////////////////////////////////////////////////////////////// //
3826 // console always has "uvar_create" command, no need to register it.
3827 public class ConCommandUserConVar : ConCommand {
3828 this () { super("uvar_create", "create user convar: userconvar \"<type> <name>\"; type is <int|str|bool|float|double>"); }
3830 override void exec (ConString cmdline) {
3831 if (checkHelp(cmdline)) { showHelp; return; }
3832 if (!hasArgs(cmdline)) return;
3833 auto type = getWord(cmdline);
3834 auto name = getWord(cmdline);
3835 if (type.length == 0 || name.length == 0) return;
3836 if (name in cmdlist) { conwriteln("console variable '", name, "' already exists"); return; }
3837 ConVarBase v;
3838 string aname = name.idup;
3839 switch (type) {
3840 case "int":
3841 int* var = new int;
3842 v = new ConVar!int(var, aname, null);
3843 break;
3844 case "str":
3845 //string* var = new string; // alas
3846 string[] var;
3847 var.length = 1;
3848 v = new ConVar!string(&var[0], aname, null);
3849 break;
3850 case "bool":
3851 bool* var = new bool;
3852 v = new ConVar!bool(var, aname, null);
3853 break;
3854 case "float":
3855 float* var = new float;
3856 v = new ConVar!float(var, aname, null);
3857 break;
3858 case "double":
3859 double* var = new double;
3860 v = new ConVar!double(var, aname, null);
3861 break;
3862 default:
3863 conwriteln("can't create console variable '", name, "' of unknown type '", type, "'");
3864 return;
3866 v.setAttrs!true(ConVarAttr.User);
3867 addName(aname);
3868 cmdlist[aname] = v;
3873 // ////////////////////////////////////////////////////////////////////////// //
3874 // console always has "uvar_remove" command, no need to register it.
3875 public class ConCommandKillUserConVar : ConCommand {
3876 this () { super("uvar_remove", "remove user convar: killuserconvar \"<name>\""); }
3878 override void exec (ConString cmdline) {
3879 if (checkHelp(cmdline)) { showHelp; return; }
3880 if (!hasArgs(cmdline)) return;
3881 auto name = getWord(cmdline);
3882 if (name.length == 0) return;
3883 if (auto vp = name in cmdlist) {
3884 if (auto var = cast(ConVarBase)(*vp)) {
3885 if (!var.attrUser) {
3886 conwriteln("console command '", name, "' is not a uservar");
3887 return;
3889 // remove it
3890 cmdlist.remove(cast(string)name); // it is safe to cast here
3891 foreach (immutable idx, string n; cmdlistSorted) {
3892 if (n == name) {
3893 foreach (immutable c; idx+1..cmdlistSorted.length) cmdlistSorted[c-1] = cmdlistSorted[c];
3894 return;
3897 } else {
3898 conwriteln("console command '", name, "' is not a var");
3899 return;
3901 } else {
3902 conwriteln("console variable '", name, "' doesn't exist");
3903 return;
3909 shared static this () {
3910 addName("echo");
3911 cmdlist["echo"] = new ConCommandEcho();
3912 addName("alias");
3913 cmdlist["alias"] = new ConCommandAlias();
3914 addName("uvar_create");
3915 cmdlist["uvar_create"] = new ConCommandUserConVar();
3916 addName("uvar_remove");
3917 cmdlist["uvar_remove"] = new ConCommandKillUserConVar();
3921 /** replace "$var" in string.
3923 * this function will replace "$var" in string with console var values.
3925 * Params:
3926 * dest = destination buffer
3927 * s = source string
3929 public char[] conFormatStr (char[] dest, ConString s) {
3930 usize dpos = 0;
3932 void put (ConString ss) {
3933 if (ss.length == 0) return;
3934 auto len = ss.length;
3935 if (dest.length-dpos < len) len = dest.length-dpos;
3936 if (len) {
3937 dest[dpos..dpos+len] = ss[];
3938 dpos += len;
3942 while (s.length) {
3943 usize pos = 0;
3944 while (pos < s.length && s.ptr[pos] != '$') ++pos;
3945 if (s.length-pos > 1 && s.ptr[pos+1] == '$') {
3946 put(s[0..pos+1]);
3947 s = s[pos+2..$];
3948 } else if (s.length-pos <= 1) {
3949 put(s);
3950 break;
3951 } else {
3952 // variable name
3953 ConString vname;
3954 if (pos > 0) put(s[0..pos]);
3955 ++pos;
3956 if (s.ptr[pos] == '{') {
3957 s = s[pos+1..$];
3958 pos = 0;
3959 while (pos < s.length && s.ptr[pos] != '}') ++pos;
3960 vname = s[0..pos];
3961 if (pos < s.length) ++pos;
3962 s = s[pos..$];
3963 } else {
3964 s = s[pos..$];
3965 pos = 0;
3966 while (pos < s.length) {
3967 char ch = s.ptr[pos];
3968 if (ch == '_' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
3969 ++pos;
3970 } else {
3971 break;
3974 vname = s[0..pos];
3975 s = s[pos..$];
3977 if (vname.length) {
3978 if (auto cc = vname in cmdlist) {
3979 if (auto cv = cast(ConVarBase)(*cc)) {
3980 auto v = cv.strval;
3981 put(v);
3982 } else {
3983 put("${!");
3984 put(vname);
3985 put("}");
3987 } else {
3988 put("${");
3989 put(vname);
3990 put("}");
3995 return dest[0..dpos];
4000 version(contest_echo) unittest {
4001 __gshared int vi = 42;
4002 __gshared string vs = "str";
4003 __gshared bool vb = true;
4004 conRegVar!vi("vi", "int var");
4005 conRegVar!vs("vs", "string var");
4006 conRegVar!vb("vb", "bool var");
4007 conRegVar!vb("r_interpolation", "bool var");
4008 conwriteln("=================");
4009 conExecute("r_interpolation");
4010 conExecute("echo ?");
4011 conExecute("echo vs=$vs, vi=${vi}, vb=${vb}!");
4013 char[44] buf;
4014 auto s = buf.conFormatStr("vs=$vs, vi=${vi}, vb=${vb}!");
4015 conwriteln("[", s, "]");
4016 foreach (/*auto*/ kv; cmdlist.byKeyValue) conwriteln(" ", kv.key);
4017 assert("r_interpolation" in cmdlist);
4018 s = buf.conFormatStr("Interpolation: $r_interpolation");
4019 conwriteln("[", s, "]");
4021 conwriteln("vi=", conGetVar!int("vi"));
4022 conwriteln("vi=", conGetVar("vi"));
4023 conwriteln("vs=", conGetVar("vi"));
4024 conwriteln("vb=", conGetVar!int("vb"));
4025 conwriteln("vb=", conGetVar!bool("vb"));
4026 conwriteln("vb=", conGetVar("vb"));
4031 version(contest_cmdlist) unittest {
4033 auto cl = conByCommand;
4034 conwriteln("=== all ===");
4035 while (!cl.empty) {
4036 if (cl.frontIsVar) conwrite("VAR ");
4037 else if (cl.frontIsFunc) conwrite("FUNC ");
4038 else conwrite("UNK ");
4039 conwriteln("[", cl.front, "]");
4040 cl.popFront();
4044 conwriteln("=== funcs ===");
4045 foreach (/*auto*/ clx; conByCommand!"funcs") conwriteln(" [", clx, "]");
4047 conwriteln("=== vars ===");
4048 foreach (/*auto*/ clx; conByCommand!"vars") conwriteln(" [", clx, "]");
4052 shared static this () {
4053 conRegFunc!(() {
4054 uint maxlen = 0;
4055 foreach (string name; cmdlistSorted[]) {
4056 if (auto ccp = name in cmdlist) {
4057 if (name.length > 64) {
4058 if (maxlen < 64) maxlen = 64;
4059 } else {
4060 if (name.length > maxlen) maxlen = cast(uint)name.length;
4064 foreach (string name; cmdlistSorted[]) {
4065 if (auto ccp = name in cmdlist) {
4066 conwrite(name);
4067 foreach (immutable _; name.length..maxlen) conwrite(" ");
4068 if (auto cmd = cast(ConCommand)(*ccp)) {
4069 conwriteln(" -- ", cmd.help);
4070 } else {
4071 conwriteln(" -- alias");
4075 })("cmdlist", "list all known commands and variables");
4079 // ////////////////////////////////////////////////////////////////////////// //
4080 // simple input buffer for console
4081 private:
4082 __gshared char[4096] concli = 0;
4083 __gshared uint conclilen = 0;
4084 __gshared int concurx = 0;
4085 public __gshared void delegate () nothrow @trusted conInputChangedCB; /// can be called in any thread
4087 shared uint inchangeCount = 1;
4088 public @property uint conInputLastChange () nothrow @trusted @nogc { pragma(inline, true); import core.atomic; return atomicLoad(inchangeCount); } /// changed when something was put to console input buffer (thread-safe)
4089 public void conInputIncLastChange () nothrow @trusted { pragma(inline, true); import core.atomic; atomicOp!"+="(inchangeCount, 1); if (conInputChangedCB !is null) conInputChangedCB(); } /// increment console input buffer change flag (thread-safe)
4091 public @property ConString conInputBuffer() () @trusted { pragma(inline, true); return concli[0..conclilen]; } /// returns console input buffer (not thread-safe)
4092 public @property int conInputBufferCurX() () @trusted { pragma(inline, true); return concurx; } /// returns cursor position in input buffer: [0..conclilen] (not thread-safe)
4095 /** clear console input buffer. (not thread-safe)
4097 * call this function with `addToHistory:true` to add current input buffer to history.
4098 * it is safe to call this for empty input buffer, history won't get empty line.
4100 * Params:
4101 * addToHistory = true if current buffer should be "added to history", so current history index should be reset and history will be updated
4103 public void conInputBufferClear() (bool addToHistory=false) @trusted {
4104 if (conclilen > 0) {
4105 if (addToHistory) conHistoryAdd(conInputBuffer);
4106 conclilen = 0;
4107 concurx = 0;
4108 conInputIncLastChange();
4110 if (addToHistory) conhisidx = -1;
4114 private struct ConHistItem {
4115 char* dbuf; // without trailing 0
4116 char[256] sbuf = 0; // static buffer
4117 uint len; // of dbuf or sbuf, without trailing 0
4118 uint alloted; // !0: `dbuf` is used
4119 nothrow @trusted @nogc:
4120 void releaseMemory () { import core.stdc.stdlib : free; if (dbuf !is null) free(dbuf); dbuf = null; alloted = len = 0; }
4121 @property const(char)[] str () { pragma(inline, true); return (alloted ? dbuf : sbuf.ptr)[0..len]; }
4122 @property void str (const(char)[] s) {
4123 import core.stdc.stdlib : malloc, realloc;
4124 if (s.length > 65536) s = s[0..65536]; // just in case
4125 if (s.length == 0) { len = 0; return; }
4126 // try to put in allocated space
4127 if (alloted) {
4128 if (s.length > alloted) {
4129 auto np = cast(char*)realloc(dbuf, s.length);
4130 if (np is null) {
4131 // can't allocate memory
4132 dbuf[0..alloted] = s[0..alloted];
4133 len = alloted;
4134 return;
4136 alloted = cast(uint)s.length;
4138 // it fits!
4139 dbuf[0..s.length] = s[];
4140 len = cast(uint)s.length;
4141 return;
4143 // fit in static buf?
4144 if (s.length <= sbuf.length) {
4145 sbuf.ptr[0..s.length] = s[];
4146 len = cast(uint)s.length;
4147 return;
4149 // allocate dynamic buffer
4150 dbuf = cast(char*)malloc(s.length);
4151 if (dbuf is null) {
4152 // alas; trim and use static one
4153 sbuf[] = s[0..sbuf.length];
4154 len = cast(uint)sbuf.length;
4155 } else {
4156 // ok, use dynamic buffer
4157 alloted = len = cast(uint)s.length;
4158 dbuf[0..len] = s[];
4164 //TODO: make circular buffer
4165 private enum ConHistMax = 8192;
4166 __gshared ConHistItem* concmdhistory = null;
4167 __gshared int conhisidx = -1;
4168 __gshared uint conhismax = 128;
4169 __gshared uint conhisused = 0;
4170 __gshared uint conhisalloted = 0;
4173 shared static this () {
4174 conRegVar!conhismax(1, ConHistMax, "r_conhistmax", "maximum commands in console input history");
4178 // free unused slots if `conhismax` was changed
4179 private void conHistShrinkBuf () {
4180 import core.stdc.stdlib : realloc;
4181 import core.stdc.string : memmove, memset;
4182 if (conhisalloted <= conhismax) return;
4183 auto tokill = conhisalloted-conhismax;
4184 debug(concmd_history) conwriteln("removing ", tokill, " items out of ", conhisalloted);
4185 // discard old items
4186 if (conhisused > conhismax) {
4187 auto todis = conhisused-conhismax;
4188 debug(concmd_history) conwriteln("discarding ", todis, " items out of ", conhisused);
4189 // free used memory
4190 foreach (ref ConHistItem it; concmdhistory[0..todis]) it.releaseMemory();
4191 // move array elements
4192 memmove(concmdhistory, concmdhistory+todis, ConHistItem.sizeof*conhismax);
4193 // clear what is left
4194 memset(concmdhistory+conhismax, 0, ConHistItem.sizeof*todis);
4195 conhisused = conhismax;
4197 // resize array
4198 auto np = cast(ConHistItem*)realloc(concmdhistory, ConHistItem.sizeof*conhismax);
4199 if (np !is null) concmdhistory = np;
4200 conhisalloted = conhismax;
4204 // allocate space for new command, return it's index or -1 on error
4205 private int conHistAllot () {
4206 import core.stdc.stdlib : realloc;
4207 import core.stdc.string : memmove, memset;
4208 conHistShrinkBuf(); // shrink buffer, if necessary
4209 if (conhisused >= conhisalloted && conhisused < conhismax) {
4210 // we need more!
4211 uint newsz = conhisalloted+64;
4212 if (newsz > conhismax) newsz = conhismax;
4213 debug(concmd_history) conwriteln("adding ", newsz-conhisalloted, " items (now: ", conhisalloted, ")");
4214 auto np = cast(ConHistItem*)realloc(concmdhistory, ConHistItem.sizeof*newsz);
4215 if (np !is null) {
4216 // yay! we did it!
4217 concmdhistory = np;
4218 // clear new items
4219 memset(concmdhistory+conhisalloted, 0, ConHistItem.sizeof*(newsz-conhisalloted));
4220 conhisalloted = newsz; // fix it! ;-)
4221 return conhisused++;
4223 // alas, have to move
4225 if (conhisalloted == 0) return -1; // no memory
4226 if (conhisalloted == 1) { conhisused = 1; return 0; } // always
4227 assert(conhisused <= conhisalloted);
4228 if (conhisused == conhisalloted) {
4229 ConHistItem tmp = concmdhistory[0];
4230 // move items up
4231 --conhisused;
4232 memmove(concmdhistory, concmdhistory+1, ConHistItem.sizeof*conhisused);
4233 //memset(concmdhistory+conhisused, 0, ConHistItem.sizeof);
4234 memmove(concmdhistory+conhisused, &tmp, ConHistItem.sizeof);
4236 return conhisused++;
4240 /// returns input buffer history item (0 is oldest) or null if there are no more items (not thread-safe)
4241 public ConString conHistoryAt (int idx) {
4242 if (idx < 0 || idx >= conhisused) return null;
4243 return concmdhistory[conhisused-idx-1].str;
4247 /// find command in input history, return index or -1 (not thread-safe)
4248 public int conHistoryFind (ConString cmd) {
4249 while (cmd.length && cmd[0] <= 32) cmd = cmd[1..$];
4250 while (cmd.length && cmd[$-1] <= 32) cmd = cmd[0..$-1];
4251 if (cmd.length == 0) return -1;
4252 foreach (int idx; 0..conhisused) {
4253 auto c = concmdhistory[idx].str;
4254 while (c.length > 0 && c[0] <= 32) c = c[1..$];
4255 while (c.length > 0 && c[$-1] <= 32) c = c[0..$-1];
4256 if (c == cmd) return conhisused-idx-1;
4258 return -1;
4262 /// add command to history. will take care about duplicate commands. (not thread-safe)
4263 public void conHistoryAdd (ConString cmd) {
4264 import core.stdc.string : memmove, memset;
4265 auto orgcmd = cmd;
4266 while (cmd.length && cmd[0] <= 32) cmd = cmd[1..$];
4267 while (cmd.length && cmd[$-1] <= 32) cmd = cmd[0..$-1];
4268 if (cmd.length == 0) return;
4269 auto idx = conHistoryFind(cmd);
4270 if (idx >= 0) {
4271 debug(concmd_history) conwriteln("command found! idx=", idx, "; real idx=", conhisused-idx-1, "; used=", conhisused);
4272 idx = conhisused-idx-1; // fix index
4273 // move command to bottom
4274 if (idx == conhisused-1) return; // nothing to do
4275 // cheatmove! ;-)
4276 ConHistItem tmp = concmdhistory[idx];
4277 // move items up
4278 memmove(concmdhistory+idx, concmdhistory+idx+1, ConHistItem.sizeof*(conhisused-idx-1));
4279 //memset(concmdhistory+conhisused, 0, ConHistItem.sizeof);
4280 memmove(concmdhistory+conhisused-1, &tmp, ConHistItem.sizeof);
4281 } else {
4282 // new command
4283 idx = conHistAllot();
4284 if (idx < 0) return; // alas
4285 concmdhistory[idx].str = orgcmd;
4290 /// special characters for `conAddInputChar()`
4291 public enum ConInputChar : char {
4292 Up = '\x01', ///
4293 Down = '\x02', ///
4294 Left = '\x03', ///
4295 Right = '\x04', ///
4296 Home = '\x05', ///
4297 End = '\x06', ///
4298 PageUp = '\x07', ///
4299 Backspace = '\x08', ///
4300 Tab = '\x09', ///
4301 // 0a
4302 PageDown = '\x0b', ///
4303 Delete = '\x0c', ///
4304 Enter = '\x0d', ///
4305 Insert = '\x0e', ///
4307 CtrlY = '\x19', ///
4308 LineUp = '\x1a', ///
4309 LineDown = '\x1b', ///
4310 CtrlW = '\x1c', ///
4314 /// process console input char (won't execute commands, but will do autocompletion and history) (not thread-safe)
4315 public void conAddInputChar (char ch) {
4316 __gshared int prevWasEmptyAndTab = 0;
4318 bool insChars (const(char)[] s...) {
4319 if (s.length == 0) return false;
4320 if (concli.length-conclilen < s.length) return false;
4321 foreach (char ch; s) {
4322 if (concurx == conclilen) {
4323 concli.ptr[conclilen++] = ch;
4324 ++concurx;
4325 } else {
4326 import core.stdc.string : memmove;
4327 memmove(concli.ptr+concurx+1, concli.ptr+concurx, conclilen-concurx);
4328 concli.ptr[concurx++] = ch;
4329 ++conclilen;
4332 return true;
4335 // autocomplete
4336 if (ch == ConInputChar.Tab) {
4337 if (concurx == 0) {
4338 if (++prevWasEmptyAndTab < 2) return;
4339 } else {
4340 prevWasEmptyAndTab = 0;
4342 if (concurx > 0) {
4343 // if there are space(s) before cursor position, this is argument completion
4344 bool doArgAC = false;
4346 int p = concurx;
4347 while (p > 0 && concli.ptr[p-1] > ' ') --p;
4348 doArgAC = (p > 0);
4350 if (doArgAC) {
4351 prevWasEmptyAndTab = 0;
4352 // yeah, arguments; first, get command name
4353 int stp = 0;
4354 while (stp < concurx && concli.ptr[stp] <= ' ') ++stp;
4355 if (stp >= concurx) return; // alas
4356 auto ste = stp+1;
4357 while (ste < concurx && concli.ptr[ste] > ' ') ++ste;
4358 if (auto cp = concli[stp..ste] in cmdlist) {
4359 if (auto cmd = cast(ConCommand)(*cp)) {
4360 if (cmd.argcomplete !is null) try { cmd.argcomplete(cmd); } catch (Exception) {} // sorry
4363 return;
4365 string minPfx = null;
4366 // find longest command
4367 foreach (/*auto*/ name; conByCommand) {
4368 if (name.length >= concurx && name.length > minPfx.length && name[0..concurx] == concli[0..concurx]) minPfx = name;
4370 string longestCmd = minPfx;
4371 //conwriteln("longest command: [", minPfx, "]");
4372 // find longest prefix
4373 foreach (/*auto*/ name; conByCommand) {
4374 if (name.length < concurx) continue;
4375 if (name[0..concurx] != concli[0..concurx]) continue;
4376 usize pos = 0;
4377 while (pos < name.length && pos < minPfx.length && minPfx.ptr[pos] == name.ptr[pos]) ++pos;
4378 if (pos < minPfx.length) minPfx = minPfx[0..pos];
4380 if (minPfx.length > concli.length) minPfx = minPfx[0..concli.length];
4381 //conwriteln("longest prefix : [", minPfx, "]");
4382 if (minPfx.length >= concurx) {
4383 // wow! has something to add
4384 bool doRet = (minPfx.length > concurx);
4385 if (insChars(minPfx[concurx..$])) {
4386 // insert space after complete command
4387 if (longestCmd.length == minPfx.length && conHasCommand(minPfx)) {
4388 if (concurx >= conclilen || concli.ptr[concurx] > ' ') {
4389 doRet = insChars(' ');
4390 } else {
4391 ++concurx;
4392 doRet = true;
4395 conInputIncLastChange();
4396 if (doRet) return;
4400 // nope, print all available commands
4401 bool needDelimiter = true;
4402 foreach (/*auto*/ name; conByCommand) {
4403 if (name.length == 0) continue;
4404 if (concurx > 0) {
4405 if (name.length < concurx) continue;
4406 if (name[0..concurx] != concli[0..concurx]) continue;
4407 } else {
4408 // skip "unusal" commands"
4409 ch = name[0];
4410 if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_')) continue;
4412 if (needDelimiter) { conwriteln("----------------"); needDelimiter = false; }
4413 conwriteln(name);
4415 return;
4417 // process other keys
4418 prevWasEmptyAndTab = 0;
4419 // remove last char
4420 if (ch == ConInputChar.Backspace) {
4421 if (concurx > 0) {
4422 if (concurx < conclilen) {
4423 import core.stdc.string : memmove;
4424 memmove(concli.ptr+concurx-1, concli.ptr+concurx, conclilen-concurx);
4426 --concurx;
4427 --conclilen;
4428 conInputIncLastChange();
4430 return;
4432 // delete char
4433 if (ch == ConInputChar.Delete) {
4434 if (concurx < conclilen) {
4435 if (conclilen > 1) {
4436 import core.stdc.string : memmove;
4437 memmove(concli.ptr+concurx, concli.ptr+concurx+1, conclilen-concurx-1);
4439 --conclilen;
4440 conInputIncLastChange();
4442 return;
4444 // ^W (delete previous word)
4445 if (ch == ConInputChar.CtrlW) {
4446 if (concurx > 0) {
4447 int stp = concurx;
4448 // skip spaces
4449 while (stp > 0 && concli.ptr[stp-1] <= ' ') --stp;
4450 // skip non-spaces
4451 while (stp > 0 && concli.ptr[stp-1] > ' ') --stp;
4452 if (stp < concurx) {
4453 import core.stdc.string : memmove;
4454 // delete from stp to concurx
4455 int dlen = concurx-stp;
4456 assert(concurx <= conclilen);
4457 if (concurx < conclilen) memmove(concli.ptr+stp, concli.ptr+concurx, conclilen-concurx);
4458 conclilen -= dlen;
4459 concurx = stp;
4460 conInputIncLastChange();
4463 return;
4465 // ^Y (delete line)
4466 if (ch == ConInputChar.CtrlY) {
4467 if (conclilen > 0) { conclilen = 0; concurx = 0; conInputIncLastChange(); }
4468 return;
4470 // home
4471 if (ch == ConInputChar.Home) {
4472 if (concurx > 0) {
4473 concurx = 0;
4474 conInputIncLastChange();
4476 return;
4478 // end
4479 if (ch == ConInputChar.End) {
4480 if (concurx < conclilen) {
4481 concurx = conclilen;
4482 conInputIncLastChange();
4484 return;
4486 // up
4487 if (ch == ConInputChar.Up) {
4488 ++conhisidx;
4489 auto cmd = conHistoryAt(conhisidx);
4490 if (cmd.length == 0) {
4491 --conhisidx;
4492 } else {
4493 concli[0..cmd.length] = cmd[];
4494 conclilen = cast(uint)cmd.length;
4495 concurx = conclilen;
4496 conInputIncLastChange();
4498 return;
4500 // down
4501 if (ch == ConInputChar.Down) {
4502 --conhisidx;
4503 auto cmd = conHistoryAt(conhisidx);
4504 if (cmd.length == 0 && conhisidx < -1) {
4505 ++conhisidx;
4506 } else {
4507 concli[0..cmd.length] = cmd[];
4508 conclilen = cast(uint)cmd.length;
4509 concurx = conclilen;
4510 conInputIncLastChange();
4512 return;
4514 // left
4515 if (ch == ConInputChar.Left) {
4516 if (concurx > 0) {
4517 --concurx;
4518 conInputIncLastChange();
4520 return;
4522 // right
4523 if (ch == ConInputChar.Right) {
4524 if (concurx < conclilen) {
4525 ++concurx;
4526 conInputIncLastChange();
4528 return;
4530 // other
4531 if (ch < ' ' || ch > 127) return;
4532 if (insChars(ch)) conInputIncLastChange();
4536 // ////////////////////////////////////////////////////////////////////////// //
4537 private:
4539 /// add console command to execution queue (thread-safe)
4540 public void concmd (ConString cmd) {
4541 consoleLock();
4542 scope(exit) { consoleUnlock(); conInputIncLastChange(); }
4543 concmdAdd(cmd);
4547 /// add console command to execution queue (thread-safe)
4548 /// this understands '%s' and '%q' (quoted string, but without surroinding quotes)
4549 /// it also understands '$var', '${var}', '${var:ifabsent}', '${var?ifempty}'
4550 /// var substitution in quotes will be automatically quoted
4551 /// string substitution in quotes will be automatically quoted
4552 public void concmdf(string fmt, A...) (A args) { concmdfdg(fmt, null, args); }
4555 /// add console command to execution queue (thread-safe)
4556 /// this understands '%s' and '%q' (quoted string, but without surroinding quotes)
4557 /// it also understands '$var', '${var}', '${var:ifabsent}', '${var?ifempty}'
4558 /// var substitution in quotes will be automatically quoted
4559 /// string substitution in quotes will be automatically quoted
4560 public void concmdfdg(A...) (ConString fmt, scope void delegate (ConString cmd) cmddg, A args) {
4561 consoleLock();
4562 scope(exit) { consoleUnlock(); conInputIncLastChange(); }
4564 auto fmtanchor = fmt;
4565 usize pos = 0;
4566 bool ensureCmd = true;
4567 char inQ = 0;
4569 usize stpos = usize.max;
4571 void puts(bool quote=false) (const(char)[] s...) {
4572 if (s.length) {
4573 if (ensureCmd) { concmdEnsureNewCommand(); ensureCmd = false; }
4574 if (stpos == usize.max) stpos = concmdbufpos;
4575 static if (quote) {
4576 char[8] buf;
4577 foreach (immutable idx, char ch; s) {
4578 if (ch < ' ' || ch == 127 || ch == '#' || ch == '"' || ch == '\\') {
4579 import core.stdc.stdio : snprintf;
4580 auto len = snprintf(buf.ptr, buf.length, "\\x%02x", cast(uint)ch);
4581 concmdAdd!false(buf[0..len]);
4582 } else {
4583 concmdAdd!false(s[idx..idx+1]);
4586 } else {
4587 concmdAdd!false(s);
4592 void putint(TT) (TT nn) {
4593 char[32] buf = ' ';
4595 static if (is(TT == shared)) {
4596 import core.atomic;
4597 alias T = XUQQ!TT;
4598 T n = atomicLoad(nn);
4599 } else {
4600 alias T = XUQQ!TT;
4601 T n = nn;
4604 static if (is(T == long)) {
4605 if (n == 0x8000_0000_0000_0000uL) { puts("-9223372036854775808"); return; }
4606 } else static if (is(T == int)) {
4607 if (n == 0x8000_0000u) { puts("-2147483648"); return; }
4608 } else static if (is(T == short)) {
4609 if ((n&0xffff) == 0x8000u) { puts("-32768"); return; }
4610 } else static if (is(T == byte)) {
4611 if ((n&0xff) == 0x80u) { puts("-128"); return; }
4614 static if (__traits(isUnsigned, T)) {
4615 enum neg = false;
4616 } else {
4617 bool neg = (n < 0);
4618 if (neg) n = -(n);
4621 int bpos = buf.length;
4622 do {
4623 //if (bpos == 0) assert(0, "internal printer error");
4624 buf.ptr[--bpos] = cast(char)('0'+n%10);
4625 n /= 10;
4626 } while (n != 0);
4627 if (neg) {
4628 //if (bpos == 0) assert(0, "internal printer error");
4629 buf.ptr[--bpos] = '-';
4631 puts(buf[bpos..$]);
4634 void putfloat(TT) (TT nn) {
4635 import core.stdc.stdlib : malloc, realloc;
4637 static if (is(TT == shared)) {
4638 import core.atomic;
4639 alias T = XUQQ!TT;
4640 T n = atomicLoad(nn);
4641 } else {
4642 alias T = XUQQ!TT;
4643 T n = nn;
4646 static char* buf;
4647 static usize buflen = 0;
4649 if (buf is null) {
4650 buflen = 256;
4651 buf = cast(char*)malloc(buflen);
4652 if (buf is null) assert(0, "out of memory");
4655 for (;;) {
4656 import core.stdc.stdio : snprintf;
4657 auto plen = snprintf(buf, buflen, "%g", cast(double)n);
4658 if (plen >= buflen) {
4659 buflen = plen+2;
4660 buf = cast(char*)realloc(buf, buflen);
4661 if (buf is null) assert(0, "out of memory");
4662 } else {
4663 puts(buf[0..plen]);
4664 return;
4669 static bool isGoodIdChar (char ch) pure nothrow @safe @nogc {
4670 pragma(inline, true);
4671 return
4672 (ch >= '0' && ch <= '9') ||
4673 (ch >= 'A' && ch <= 'Z') ||
4674 (ch >= 'a' && ch <= 'z') ||
4675 ch == '_';
4678 void processUntilFSp () {
4679 while (pos < fmt.length) {
4680 usize epos = pos;
4681 while (epos < fmt.length && fmt[epos] != '%') {
4682 if (fmt[epos] == '\\' && fmt.length-epos > 1 /*&& (fmt[epos+1] == '"' || fmt[epos+1] == '$')*/) {
4683 puts(fmt[pos..epos]);
4684 puts(fmt[epos+1]);
4685 pos = (epos += 2);
4686 continue;
4688 if (inQ && fmt[epos] == inQ) inQ = 0;
4689 if (fmt[epos] == '"') {
4690 inQ = '"';
4691 } else if (fmt[epos] == '$') {
4692 puts(fmt[pos..epos]);
4693 pos = epos;
4694 if (fmt.length-epos > 0 && fmt[epos+1] != '{') {
4695 // simple
4696 ++epos;
4697 while (epos < fmt.length && isGoodIdChar(fmt[epos])) ++epos;
4698 if (epos-pos > 1) {
4699 if (auto cc = fmt[pos+1..epos] in cmdlist) {
4700 if (auto cv = cast(ConVarBase)(*cc)) {
4701 if (inQ) puts!true(cv.strval); else puts!false(cv.strval);
4702 pos = epos;
4703 continue;
4707 // go on
4708 } else {
4709 // complex
4710 epos += 2;
4711 //FIXME: allow escaping of '}'
4712 while (epos < fmt.length && fmt[epos] != '}') ++epos;
4713 if (epos >= fmt.length) { epos = pos+1; continue; } // no '}'
4714 pos += 2;
4715 usize cpos = pos;
4716 while (cpos < epos && fmt[cpos] != ':' && fmt[cpos] != '?') ++cpos;
4717 if (auto cc = fmt[pos..cpos] in cmdlist) {
4718 if (auto cv = cast(ConVarBase)(*cc)) {
4719 auto sv = cv.strval;
4720 // process replacement value for empty strings
4721 if (cpos < epos && fmt[cpos] == '?' && sv.length == 0) {
4722 if (inQ) puts!true(fmt[cpos+1..epos]); else puts!false(fmt[cpos+1..epos]);
4723 } else {
4724 if (inQ) puts!true(sv); else puts!false(sv);
4726 pos = epos+1;
4727 continue;
4730 // either no such command, or it's not a var
4731 if (cpos < epos && (fmt[cpos] == '?' || fmt[cpos] == ':')) {
4732 if (inQ) puts!true(fmt[cpos+1..epos]); else puts!false(fmt[cpos+1..epos]);
4734 pos = epos+1;
4735 continue;
4738 ++epos;
4740 if (epos > pos) {
4741 puts(fmt[pos..epos]);
4742 pos = epos;
4743 if (pos >= fmt.length) break;
4745 if (fmt.length-pos < 2 || fmt[pos+1] == '%') { puts('%'); pos += 2; continue; }
4746 break;
4750 foreach (immutable argnum, /*auto*/ att; A) {
4751 processUntilFSp();
4752 if (pos >= fmt.length) assert(0, "out of format specifiers for arguments");
4753 assert(fmt[pos] == '%');
4754 ++pos;
4755 if (pos >= fmt.length) assert(0, "out of format specifiers for arguments");
4756 alias at = XUQQ!att;
4757 bool doQuote = false;
4758 switch (fmt[pos++]) {
4759 case 'q':
4760 static if (is(at == char)) {
4761 puts!true(args[argnum]);
4762 } else static if (is(at == wchar) || is(at == dchar)) {
4763 import std.conv : to;
4764 puts!true(to!string(args[argnum]));
4765 } else static if (is(at == bool)) {
4766 puts!false(args[argnum] ? "true" : "false");
4767 } else static if (is(at == enum)) {
4768 bool dumpNum = true;
4769 foreach (string mname; __traits(allMembers, at)) {
4770 if (args[argnum] == __traits(getMember, at, mname)) {
4771 puts(mname);
4772 dumpNum = false;
4773 break;
4776 //FIXME: check sign
4777 if (dumpNum) putint!long(cast(long)args[argnum]);
4778 } else static if (is(at == float) || is(at == double) || is(at == real)) {
4779 putfloat(args[argnum]);
4780 } else static if (is(at : const(char)[])) {
4781 puts!true(args[argnum]);
4782 } else static if (is(at : T*, T)) {
4783 assert(0, "can't put pointers");
4784 } else {
4785 import std.conv : to;
4786 puts!true(to!string(args[argnum]));
4788 break;
4789 case 's':
4790 case 'd': // oops
4791 case 'u': // oops
4792 if (inQ) goto case 'q';
4793 static if (is(at == char)) {
4794 puts(args[argnum]);
4795 } else static if (is(at == wchar) || is(at == dchar)) {
4796 import std.conv : to;
4797 puts(to!string(args[argnum]));
4798 } else static if (is(at == bool)) {
4799 puts(args[argnum] ? "true" : "false");
4800 } else static if (is(at == enum)) {
4801 bool dumpNum = true;
4802 foreach (string mname; __traits(allMembers, at)) {
4803 if (args[argnum] == __traits(getMember, at, mname)) {
4804 puts(mname);
4805 dumpNum = false;
4806 break;
4809 //FIXME: check sign
4810 if (dumpNum) putint!long(cast(long)args[argnum]);
4811 } else static if (is(at == float) || is(at == double) || is(at == real)) {
4812 putfloat(args[argnum]);
4813 } else static if (is(at : const(char)[])) {
4814 puts(args[argnum]);
4815 } else static if (is(at : T*, T)) {
4816 assert(0, "can't put pointers");
4817 } else {
4818 import std.conv : to;
4819 puts(to!string(args[argnum]));
4821 break;
4822 default:
4823 assert(0, "invalid format specifier");
4827 while (pos < fmt.length) {
4828 processUntilFSp();
4829 if (pos >= fmt.length) break;
4830 assert(0, "out of args for format specifier");
4833 //conwriteln(concmdbuf[0..concmdbufpos]);
4834 if (cmddg !is null) {
4835 if (stpos != usize.max) {
4836 cmddg(concmdbuf[stpos..concmdbufpos]);
4837 } else {
4838 cmddg("");
4844 /// get console variable value; doesn't do complex conversions! (thread-safe)
4845 public T convar(T) (ConString s) {
4846 consoleLock();
4847 scope(exit) consoleUnlock();
4848 return conGetVar!T(s);
4851 /// set console variable value; doesn't do complex conversions! (thread-safe)
4852 /// WARNING! this is instant action, execution queue and r/o (and other) flags are ignored!
4853 public void convar(T) (ConString s, T val) {
4854 consoleLock();
4855 scope(exit) consoleUnlock();
4856 conSetVar!T(s, val);
4860 // ////////////////////////////////////////////////////////////////////////// //
4861 __gshared char[] concmdbuf;
4862 __gshared uint concmdbufpos;
4863 shared static this () { concmdbuf.unsafeArraySetLength(65536); }
4866 private void unsafeArraySetLength(T) (ref T[] arr, int newlen) /*nothrow*/ {
4867 if (newlen < 0 || newlen >= int.max/2) assert(0, "invalid number of elements in array");
4868 if (arr.length > newlen) {
4869 arr.length = newlen;
4870 arr.assumeSafeAppend;
4871 } else if (arr.length < newlen) {
4872 auto optr = arr.ptr;
4873 arr.length = newlen;
4874 if (arr.ptr !is optr) {
4875 import core.memory : GC;
4876 optr = arr.ptr;
4877 if (optr is GC.addrOf(optr)) GC.setAttr(optr, GC.BlkAttr.NO_INTERIOR);
4883 void concmdPrepend (ConString s) {
4884 if (s.length == 0) return; // nothing to do
4885 if (s.length >= int.max/4) assert(0, "console command too long"); //FIXME
4886 uint reslen = cast(uint)s.length+1;
4887 uint newlen = concmdbufpos+reslen;
4888 if (newlen <= concmdbufpos || newlen >= int.max/2-1024) assert(0, "console command too long"); //FIXME
4889 if (newlen > concmdbuf.length) concmdbuf.unsafeArraySetLength(newlen+512);
4890 // make room
4891 if (concmdbufpos > 0) {
4892 import core.stdc.string : memmove;
4893 memmove(concmdbuf.ptr+reslen, concmdbuf.ptr, concmdbufpos);
4895 // put new test and '\n'
4896 concmdbuf.ptr[0..s.length] = s[];
4897 concmdbuf.ptr[s.length] = '\n';
4898 concmdbufpos += reslen;
4902 void concmdEnsureNewCommand () {
4903 if (concmdbufpos > 0 && concmdbuf[concmdbufpos-1] != '\n') {
4904 if (concmdbuf.length-concmdbufpos < 1) concmdbuf.unsafeArraySetLength(cast(int)concmdbuf.length+512);
4906 concmdbuf.ptr[concmdbufpos++] = '\n';
4910 package(iv) void concmdAdd(bool ensureNewCommand=true) (ConString s) {
4911 if (s.length) {
4912 if (concmdbuf.length-concmdbufpos < s.length+1) {
4913 concmdbuf.unsafeArraySetLength(cast(int)(concmdbuf.length+s.length-(concmdbuf.length-concmdbufpos)+512));
4915 static if (ensureNewCommand) {
4916 if (concmdbufpos > 0 && concmdbuf[concmdbufpos-1] != '\n') concmdbuf.ptr[concmdbufpos++] = '\n';
4918 concmdbuf[concmdbufpos..concmdbufpos+s.length] = s[];
4919 concmdbufpos += s.length;
4924 /** does console has anything in command queue? (not thread-safe)
4926 * note that it can be anything, including comment or some blank chars.
4928 * Returns:
4929 * `true` is queue is empty.
4931 public bool conQueueEmpty () {
4932 return (concmdbufpos == 0);
4936 /** execute commands added with `concmd()`. (thread-safe)
4938 * all commands added during execution of this function will be postponed for the next call.
4939 * call this function in your main loop to process all accumulated console commands.
4941 * WARNING:
4943 * Params:
4944 * maxlen = maximum queue size to process (0: don't process commands added while processing ;-)
4946 * Returns:
4947 * "has more commands" flag (i.e. some new commands were added to queue)
4949 public bool conProcessQueue (uint maxlen=0) {
4950 import core.stdc.stdlib : realloc;
4951 static char* tmpcmdbuf = null;
4952 static uint tmpcmdbufsize = 0;
4953 scope(exit) { consoleLock(); clearPrependStr(); conHistShrinkBuf(); consoleUnlock(); } // do it here
4955 consoleLock();
4956 scope(exit) consoleUnlock();
4957 if (concmdbufpos == 0) return false;
4959 bool once = (maxlen == 0);
4960 if (once) maxlen = uint.max;
4961 for (;;) {
4962 ConString s;
4964 consoleLock();
4965 scope(exit) consoleUnlock();
4966 auto ebuf = concmdbufpos;
4967 s = concmdbuf[0..ebuf];
4968 version(iv_cmdcon_debug_wait) conwriteln("** [", s, "]; ebuf=", ebuf);
4969 if (tmpcmdbufsize < s.length) {
4970 tmpcmdbuf = cast(char*)realloc(tmpcmdbuf, s.length+1);
4971 if (tmpcmdbuf is null) {
4972 // here, we are dead and fucked (the exact order doesn't matter)
4973 import core.stdc.stdlib : abort;
4974 import core.stdc.stdio : fprintf, stderr;
4975 import core.memory : GC;
4976 import core.thread;
4977 GC.disable(); // yeah
4978 thread_suspendAll(); // stop right here, you criminal scum!
4979 fprintf(stderr, "\n=== FATAL: OUT OF MEMORY IN COMMAND CONSOLE!\n");
4980 abort(); // die, you bitch!
4982 tmpcmdbufsize = cast(uint)s.length+1;
4984 import core.stdc.string : memcpy;
4985 if (s.length) { memcpy(tmpcmdbuf, s.ptr, s.length); s = tmpcmdbuf[0..s.length]; }
4986 if (!once) {
4987 if (maxlen <= ebuf) maxlen = 0; else maxlen -= ebuf;
4989 concmdbufpos = 0;
4990 if (s.length == 0) break;
4992 // process commands
4993 while (s.length) {
4994 auto cmd = conGetCommandStr(s);
4995 if (cmd is null) break;
4996 if (cmd.length > int.max/8) { conwriteln("command too long"); continue; }
4997 try {
4998 version(iv_cmdcon_debug_wait) conwriteln("** <", cmd, ">");
4999 if (conExecute(cmd)) {
5000 // "wait" pseudocommand hit; prepend what is left
5001 version(iv_cmdcon_debug_wait) conwriteln(" :: rest=<", s, ">");
5002 consoleLock();
5003 scope(exit) consoleUnlock();
5004 if (ccWaitPrependStrLen) { concmdPrepend(prependStr); clearPrependStr(); }
5005 if (s.length) { setPrependStr(s); concmdPrepend(prependStr); clearPrependStr(); }
5006 once = true;
5007 break;
5009 version(iv_cmdcon_debug_wait) conwriteln(" ++++++++++");
5010 } catch (Exception e) {
5011 conwriteln("***ERROR: ", e.msg);
5014 if (once || maxlen == 0) break;
5017 consoleLock();
5018 scope(exit) consoleUnlock();
5019 if (ccWaitPrependStrLen) { concmdPrepend(prependStr); clearPrependStr(); }
5020 return (concmdbufpos == 0);
5025 // ////////////////////////////////////////////////////////////////////////// //
5026 /** process command-line arguments, put 'em to console queue, remove from array (thread-safe).
5028 * just call this function from `main (string[] args)`, with `args`.
5030 * console args looks like:
5031 * +cmd arg arg +cmd arg arg
5033 * Params:
5034 * immediate = immediately call `conProcessQueue()` with some big limit
5035 * args = those arg vector passed to `main()`
5037 * Returns:
5038 * `true` is any command was added to queue.
5040 public bool conProcessArgs(bool immediate=false) (ref string[] args) {
5041 //consoleLock();
5042 //scope(exit) consoleUnlock();
5044 bool ensureCmd = true;
5045 auto ocbpos = concmdbufpos;
5047 void puts (const(char)[] s...) {
5048 if (s.length) {
5049 if (ensureCmd) {
5050 concmdEnsureNewCommand();
5051 ensureCmd = false;
5052 } else {
5053 concmdAdd!false(" "); // argument delimiter
5055 // check if we need to quote arg
5056 bool doQuote = false;
5057 foreach (char ch; s) if (ch <= ' ' || ch == 127 || ch == '"' || ch == '#') { doQuote = true; break; }
5058 if (doQuote) {
5059 concmdAdd!false("\"");
5060 foreach (immutable idx, char ch; s) {
5061 if (ch < ' ' || ch == 127 || ch == '"' || ch == '\\') {
5062 import core.stdc.stdio : snprintf;
5063 char[8] buf = 0;
5064 auto len = snprintf(buf.ptr, buf.length, "\\x%02x", cast(uint)ch);
5065 if (len <= 0) assert(0, "concmd: ooooops!");
5066 concmdAdd!false(buf[0..len]);
5067 } else {
5068 concmdAdd!false(s[idx..idx+1]);
5071 concmdAdd!false("\"");
5072 } else {
5073 concmdAdd!false(s);
5078 static if (immediate) bool res = false;
5080 usize idx = 1;
5081 while (idx < args.length) {
5082 string a = args[idx++];
5083 if (a.length == 0) continue;
5084 if (a == "--") break; // no more
5085 if (a == "++" || a == "+--") {
5086 // normal parsing, remove this
5087 --idx;
5088 foreach (immutable c; idx+1..args.length) args[c-1] = args[c];
5089 args.length -= 1;
5090 continue;
5092 if (a[0] == '+') {
5093 scope(exit) ensureCmd = true;
5094 auto xidx = idx-1;
5095 puts(a[1..$]);
5096 while (idx < args.length) {
5097 a = args[idx];
5098 if (a.length > 0) {
5099 if (a[0] == '+') break;
5100 puts(a);
5102 ++idx;
5104 foreach (immutable c; idx..args.length) args[xidx+c-idx] = args[c];
5105 args.length -= idx-xidx;
5106 idx = xidx;
5107 // execute it immediately if we're asked to do so
5108 static if (immediate) {
5109 if (concmdbufpos > ocbpos) {
5110 res = true;
5111 conProcessQueue(256*1024);
5112 ocbpos = concmdbufpos;
5118 debug(concmd_procargs) {
5119 import core.stdc.stdio : snprintf;
5120 import core.sys.posix.unistd : STDERR_FILENO, write;
5121 if (concmdbufpos > ocbpos) {
5122 write(STDERR_FILENO, "===\n".ptr, 4);
5123 write(STDERR_FILENO, concmdbuf.ptr+ocbpos, concmdbufpos-ocbpos);
5124 write(STDERR_FILENO, "\n".ptr, 1);
5126 foreach (immutable aidx, string a; args) {
5127 char[16] buf;
5128 auto len = snprintf(buf.ptr, buf.length, "%u: ", cast(uint)aidx);
5129 write(STDERR_FILENO, buf.ptr, len);
5130 write(STDERR_FILENO, a.ptr, a.length);
5131 write(STDERR_FILENO, "\n".ptr, 1);
5135 static if (immediate) return res; else return (concmdbufpos > ocbpos);
5139 // ////////////////////////////////////////////////////////////////////////// //
5140 // "exec" command
5141 static if (__traits(compiles, (){import iv.vfs;}())) {
5142 enum CmdConHasVFS = true;
5143 import iv.vfs;
5144 } else {
5145 enum CmdConHasVFS = false;
5146 import std.stdio : File;
5148 private int xindexof (const(char)[] s, const(char)[] pat, int stpos=0) nothrow @trusted @nogc {
5149 if (pat.length == 0 || pat.length > s.length) return -1;
5150 if (stpos < 0) stpos = 0;
5151 if (s.length > int.max/4) s = s[0..int.max/4];
5152 while (stpos < s.length) {
5153 if (s.length-stpos < pat.length) break;
5154 if (s.ptr[stpos] == pat.ptr[0]) {
5155 if (s.ptr[stpos..stpos+pat.length] == pat[]) return stpos;
5157 ++stpos;
5159 return -1;
5162 shared static this () {
5163 conRegFunc!((ConString fname, bool silent=false) {
5164 try {
5165 static if (CmdConHasVFS) {
5166 auto fl = VFile(fname);
5167 } else {
5168 auto fl = File(fname.idup);
5170 auto sz = fl.size;
5171 if (sz > 1024*1024*64) throw new Exception("script file too big");
5172 if (sz > 0) {
5173 enum AbortCmd = "!!abort!!";
5174 auto s = new char[](cast(uint)sz);
5175 static if (CmdConHasVFS) {
5176 fl.rawReadExact(s);
5177 } else {
5178 auto tbuf = s;
5179 while (tbuf.length) {
5180 auto rd = fl.rawRead(tbuf);
5181 if (rd.length == 0) throw new Exception("read error");
5182 tbuf = tbuf[rd.length..$];
5185 if (s.xindexof(AbortCmd) >= 0) {
5186 auto apos = s.xindexof(AbortCmd);
5187 while (apos >= 0) {
5188 if (s.length-apos <= AbortCmd.length || s[apos+AbortCmd.length] <= ' ') {
5189 bool good = true;
5190 // it should be the first command, not commented
5191 auto pos = apos;
5192 while (pos > 0 && s[pos-1] != '\n') {
5193 if (s[pos-1] != ' ' && s[pos-1] != '\t') { good = false; break; }
5194 --pos;
5196 if (good) { s = s[0..apos]; break; }
5198 // check next
5199 apos = s.xindexof(AbortCmd, apos+1);
5202 concmd(s);
5204 } catch (Exception e) {
5205 if (!silent) conwriteln("ERROR loading script \"", fname, "\"");
5207 })("exec", "execute console script (name [silent_failure_flag])");