some updates
[iv.d.git] / txtser.d
blobdbd7a904bfead68beb152ee9edc0eef187cf2c73
1 /* Written 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 /// very simple (de)serializer to json-like text format
17 module iv.txtser /*is aliced*/;
18 private:
20 import std.range : ElementEncodingType, isInputRange, isOutputRange;
21 import std.traits : Unqual;
22 import iv.alice;
23 import iv.vfs;
26 // ////////////////////////////////////////////////////////////////////////// //
27 public enum SRZIgnore; /// ignore this field
28 public struct SRZName { string name; } /// rename this field
29 public enum SRZNonDefaultOnly; /// write only if it has non-default value
32 // ////////////////////////////////////////////////////////////////////////// //
33 template arrayElementType(T) {
34 private import std.traits : isArray, Unqual;
35 static if (isArray!T) {
36 alias arrayElementType = arrayElementType!(typeof(T.init[0]));
37 } else static if (is(typeof(T))) {
38 alias arrayElementType = Unqual!(typeof(T));
39 } else {
40 alias arrayElementType = Unqual!T;
43 static assert(is(arrayElementType!string == char));
45 template isSimpleType(T) {
46 private import std.traits : Unqual;
47 private alias UT = Unqual!T;
48 enum isSimpleType = __traits(isIntegral, UT) || __traits(isFloating, UT) || is(UT == bool);
51 template isCharType(T) {
52 private import std.traits : Unqual;
53 private alias UT = Unqual!T;
54 enum isCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
58 // ////////////////////////////////////////////////////////////////////////// //
59 ///
60 public void txtser(T, ST) (in auto ref T v, auto ref ST fl, int indent=0, bool skipstname=false)
61 if (!is(T == class) && (isWriteableStream!ST || isOutputRange!(ST, char)))
63 enum Indent = 2;
65 void xput (const(char)[] s...) {
66 if (s.length == 0) return;
67 static if (isWriteableStream!ST) {
68 fl.rawWrite(s[]);
69 } else {
70 static if (is(typeof(fl.put(s)))) {
71 fl.put(s);
72 } else {
73 foreach (char ch; s) fl.put(ch);
78 void quote (const(char)[] s) {
79 static immutable string hexd = "0123456789abcdef";
80 xput('"');
81 bool goodString = true;
82 foreach (char ch; s) if (ch < ' ' || ch == 127 || ch == '"' || ch == '\\') { goodString = false; break; }
83 if (goodString) {
84 // easy deal
85 xput(s);
86 } else {
87 // hard time
88 usize pos = 0;
89 while (pos < s.length) {
90 auto epos = pos;
91 while (epos < s.length) {
92 auto ch = s.ptr[epos];
93 if (ch < ' ' || ch == 127 || ch == '"' || ch == '\\') break;
94 ++epos;
96 if (epos > pos) {
97 xput(s[pos..epos]);
98 pos = epos;
100 if (pos < s.length) {
101 auto ch = s.ptr[pos++];
102 if (ch < ' ' || ch == 127 || ch == '"' || ch == '\\') {
103 switch (ch) {
104 case '\x1b': xput(`\e`); break;
105 case '\r': xput(`\r`); break;
106 case '\n': xput(`\n`); break;
107 case '\t': xput(`\t`); break;
108 case '"': case '\\': xput(`\`); xput(ch); break;
109 default:
110 xput(`\x`);
111 xput(hexd[(ch>>4)&0x0f]);
112 xput(hexd[ch&0x0f]);
113 break;
115 } else {
116 xput(ch);
121 xput('"');
124 void newline () {
125 xput('\n');
126 foreach (immutable _; 0..indent) xput(' ');
129 void serData(T) (in ref T v, bool skipstructname) {
130 alias UT = arrayElementType!T;
131 static if (is(T : const(char)[])) {
132 // string
133 static if (is(T == string) || is(T == const(char)[])) {
134 if (v.ptr is null) xput("null"); else quote(v);
135 } else {
136 quote(v);
138 } else static if (is(T : V[], V)) {
139 // array
140 if (v.length) {
141 xput("[");
142 indent += Indent;
143 foreach (immutable idx, const ref it; v) {
144 if (idx == 0 || (v.length >= 56 && idx%42 == 41)) newline();
145 serData(it, true);
146 if (idx != v.length-1) xput(",");
148 indent -= Indent;
149 newline;
150 xput("]");
151 } else {
152 xput("[]");
154 } else static if (is(T : V[K], K, V)) {
155 // associative array
156 if (v.length) {
157 xput("{");
158 indent += Indent;
159 auto len = v.length;
160 foreach (const kv; v.byKeyValue) {
161 newline;
162 serData(kv.key, true);
163 xput(": ");
164 serData(kv.value, true);
165 if (--len) xput(",");
167 indent -= Indent;
168 newline;
169 xput("}");
170 } else {
171 xput("{}");
173 } else static if (isCharType!UT) {
174 import std.conv : to;
175 xput((cast(uint)v).to!string);
176 } else static if (is(UT == enum)) {
177 bool enumFound = false;
178 foreach (string fldname; __traits(allMembers, UT)) {
179 if (v == __traits(getMember, UT, fldname)) {
180 //xput(UT.stringof);
181 //xput(".");
182 xput(fldname);
183 enumFound = true;
184 break;
187 if (!enumFound) {
188 import std.conv : to;
189 xput(v.to!string);
191 } else static if (isSimpleType!UT) {
192 import std.conv : to;
193 xput(v.to!string);
194 } else static if (is(UT == struct)) {
195 import std.traits : FieldNameTuple, getUDAs, hasUDA;
196 if (skipstructname) {
197 xput("{");
198 } else {
199 xput(UT.stringof);
200 xput(": {");
202 indent += Indent;
203 bool needComma = false;
204 foreach (string fldname; FieldNameTuple!UT) {
205 static if (!hasUDA!(__traits(getMember, UT, fldname), SRZIgnore)) {
206 enum names = getUDAs!(__traits(getMember, UT, fldname), SRZName);
207 static if (names.length) enum xname = names[0].name; else enum xname = fldname;
208 static assert(xname.length <= 255, "struct '"~UT.stringof~"': field name too long: "~xname);
209 static if (hasUDA!(__traits(getMember, UT, fldname), SRZNonDefaultOnly)) {
210 if (__traits(getMember, v, fldname) == __traits(getMember, v, fldname).init) continue;
212 if (needComma) xput(",");
213 newline;
214 xput(xname);
215 xput(": ");
216 serData(__traits(getMember, v, fldname), true);
217 needComma = true;
220 indent -= Indent;
221 newline;
222 xput("}");
223 } else {
224 static assert(0, "can't serialize type '"~T.stringof~"'");
228 serData(v, skipstname);
232 // ////////////////////////////////////////////////////////////////////////// //
234 public enum isGoodSerParser(T) = is(typeof((inout int=0) {
235 auto t = T.init;
236 char ch = t.curch;
237 ch = t.peek;
238 t.skipChar();
239 bool b = t.eot;
240 t.skipBlanks();
241 int l = t.line;
242 int c = t.col;
243 const(char)[] s = t.expectId!true();
244 s = t.expectId!false;
245 b = T.isGoodIdChar(' ');
246 t.expectChar('!');
247 int d = T.digitInBase('1', 10);
248 t.error("message");
249 t.parseString!true(delegate (char ch) {});
250 t.parseString!false(delegate (char ch) {});
251 }));
255 public struct TxtSerParser(ST) if (isReadableStream!ST || (isInputRange!ST && is(Unqual!(ElementEncodingType!ST) == char))) {
256 private:
257 ST st;
258 int eotflag; // 0: not; 1: at peek; -1: at front; -2: done
259 // buffer for identifier reading
260 char[128] buf = 0;
261 int bpos = 0;
262 static if (isReadableStream!ST) {
263 enum AsStream = true;
264 char[256] rdbuf = 0;
265 uint rdpos, rdused;
266 } else {
267 enum AsStream = false;
270 public:
271 int line, col;
272 char curch, peek;
274 public:
275 this() (auto ref ST stream) {
276 st = stream;
277 // load first chars
278 skipChar();
279 skipChar();
280 line = 1;
281 col = 1;
284 void error (string msg) { import std.conv : to; throw new Exception(msg~" around line "~line.to!string~", column "~col.to!string); }
286 @property bool eot () const pure nothrow @safe @nogc { pragma(inline, true); return (eotflag < -1); }
288 void skipChar () {
289 if (eotflag < 0) { curch = peek = 0; eotflag = -2; return; }
290 if (curch == '\n') { ++line; col = 1; } else ++col;
291 curch = peek;
292 if (eotflag > 0) { eotflag = -1; peek = 0; return; }
293 // read next char to `peek`
294 static if (AsStream) {
295 if (rdpos >= rdused) {
296 auto read = st.rawRead(rdbuf[]);
297 if (read.length == 0) {
298 peek = 0;
299 eotflag = 1;
300 return;
302 rdpos = 0;
303 rdused = cast(uint)read.length;
305 assert(rdpos < rdused);
306 peek = rdbuf.ptr[rdpos++];
307 } else {
308 if (!st.empty) {
309 peek = st.front;
310 st.popFront;
311 } else {
312 peek = 0;
313 eotflag = 1;
318 void skipBlanks () {
319 while (!eot) {
320 if ((curch == '/' && peek == '/') || curch == '#') {
321 while (!eot && curch != '\n') skipChar();
322 } else if (curch == '/' && peek == '*') {
323 skipChar();
324 skipChar();
325 while (!eot) {
326 if (curch == '*' && peek == '/') {
327 skipChar();
328 skipChar();
329 break;
332 } else if (curch == '/' && peek == '+') {
333 skipChar();
334 skipChar();
335 int level = 1;
336 while (!eot) {
337 if (curch == '+' && peek == '/') {
338 skipChar();
339 skipChar();
340 if (--level == 0) break;
341 } else if (curch == '/' && peek == '+') {
342 skipChar();
343 skipChar();
344 ++level;
347 } else if (curch > ' ') {
348 break;
349 } else {
350 skipChar();
355 void expectChar (char ch) {
356 skipBlanks();
357 if (eot || curch != ch) error("'"~ch~"' expected");
358 skipChar();
361 const(char)[] expectId(bool allowQuoted=false) () {
362 bpos = 0;
363 skipBlanks();
364 static if (allowQuoted) {
365 if (!eot && curch == '"') {
366 skipChar();
367 while (!eot && curch != '"') {
368 if (curch == '\\') error("simple string expected");
369 if (bpos >= buf.length) error("identifier or number too long");
370 buf.ptr[bpos++] = curch;
371 skipChar();
373 if (eot || curch != '"') error("simple string expected");
374 skipChar();
375 return buf[0..bpos];
378 if (!isGoodIdChar(curch)) error("identifier or number expected");
379 while (isGoodIdChar(curch)) {
380 if (bpos >= buf.length) error("identifier or number too long");
381 buf[bpos++] = curch;
382 skipChar();
384 return buf[0..bpos];
387 // `curch` is opening quote
388 void parseString(bool allowEscapes) (scope void delegate (char ch) put) {
389 assert(put !is null);
390 if (eot) error("unterminated string");
391 char qch = curch;
392 skipChar();
393 while (!eot && curch != qch) {
394 static if (allowEscapes) if (curch == '\\') {
395 skipChar();
396 if (eot) error("unterminated string");
397 switch (curch) {
398 case '0': // oops, octal
399 uint ucc = 0;
400 foreach (immutable _; 0..4) {
401 if (eot) error("unterminated string");
402 int dig = digitInBase(curch, 10);
403 if (dig < 0) break;
404 if (dig > 7) error("invalid octal escape");
405 ucc = ucc*8+dig;
406 skipChar();
408 if (ucc > 255) error("invalid octal escape");
409 put(cast(char)ucc);
410 break;
411 case '1': .. case '9': // decimal
412 uint ucc = 0;
413 foreach (immutable _; 0..3) {
414 if (eot) error("unterminated string");
415 int dig = digitInBase(curch, 10);
416 if (dig < 0) break;
417 ucc = ucc*10+dig;
418 skipChar();
420 if (ucc > 255) error("invalid decimal escape");
421 put(cast(char)ucc);
422 break;
423 case 'e': put('\x1b'); skipChar(); break;
424 case 'r': put('\r'); skipChar(); break;
425 case 'n': put('\n'); skipChar(); break;
426 case 't': put('\t'); skipChar(); break;
427 case '"': case '\\': case '\'': put(curch); skipChar(); break;
428 case 'x':
429 case 'X':
430 if (eot) error("unterminated string");
431 if (digitInBase(peek, 16) < 0) error("invalid hex escape");
432 skipChar(); // skip 'x'
433 if (eot) error("unterminated string");
434 if (digitInBase(curch, 16) < 0 || digitInBase(peek, 16) < 0) error("invalid hex escape");
435 put(cast(char)(digitInBase(curch, 16)*16+digitInBase(peek, 16)));
436 skipChar();
437 skipChar();
438 break;
439 case 'u':
440 case 'U':
441 if (digitInBase(peek, 16) < 0) error("invalid unicode escape");
442 uint ucc = 0;
443 skipChar(); // skip 'u'
444 foreach (immutable _; 0..4) {
445 if (eot) error("unterminated string");
446 if (digitInBase(curch, 16) < 0) break;
447 ucc = ucc*16+digitInBase(curch, 16);
448 skipChar();
450 char[4] buf = void;
451 auto len = utf8Encode(buf[], cast(dchar)ucc);
452 assert(len != 0);
453 if (len < 0) error("invalid utf-8 escape");
454 foreach (char ch; buf[0..len]) put(ch);
455 break;
456 default: error("invalid escape");
458 continue;
460 // normal char
461 put(curch);
462 skipChar();
464 if (eot || curch != qch) error("unterminated string");
465 skipChar();
468 static pure nothrow @safe @nogc:
469 bool isGoodIdChar (char ch) {
470 pragma(inline, true);
471 return
472 (ch >= '0' && ch <= '9') ||
473 (ch >= 'A' && ch <= 'Z') ||
474 (ch >= 'a' && ch <= 'z') ||
475 ch == '_' || ch == '-' || ch == '+' || ch == '.';
478 int digitInBase (char ch, int base) {
479 pragma(inline, true);
480 return
481 base >= 1 && ch >= '0' && ch < '0'+base ? ch-'0' :
482 base > 10 && ch >= 'A' && ch < 'A'+base-10 ? ch-'A'+10 :
483 base > 10 && ch >= 'a' && ch < 'a'+base-10 ? ch-'a'+10 :
489 static assert(isGoodSerParser!(TxtSerParser!VFile));
492 // ////////////////////////////////////////////////////////////////////////// //
494 public void txtunser(bool ignoreUnknown=false, T, ST) (out T v, auto ref ST fl)
495 if (!is(T == class) && (isReadableStream!ST || (isInputRange!ST && is(Unqual!(ElementEncodingType!ST) == char))))
497 auto par = TxtSerParser!ST(fl);
498 txtunser!ignoreUnknown(v, par);
503 public void txtunser(bool ignoreUnknown=false, T, ST) (out T v, auto ref ST par) if (!is(T == class) && isGoodSerParser!ST) {
504 import std.traits : Unqual;
506 void skipComma () {
507 par.skipBlanks();
508 if (par.curch == ',') par.skipChar();
511 static if (ignoreUnknown) void skipData(bool fieldname) () {
512 par.skipBlanks();
513 // string?
514 if (par.curch == '"' || par.curch == '\'') {
515 par.parseString!(!fieldname)(delegate (char ch) {});
516 return;
518 static if (!fieldname) {
519 // array?
520 if (par.curch == '[') {
521 par.skipChar();
522 for (;;) {
523 par.skipBlanks();
524 if (par.eot) par.error("unterminated array");
525 if (par.curch == ']') break;
526 skipData!false();
527 skipComma();
529 par.expectChar(']');
530 return;
532 // dictionary?
533 if (par.curch == '{') {
534 par.skipChar();
535 for (;;) {
536 par.skipBlanks();
537 if (par.eot) par.error("unterminated array");
538 if (par.curch == '}') break;
539 skipData!true(); // field name
540 par.skipBlanks();
541 par.expectChar(':');
542 skipData!false();
543 skipComma();
545 par.expectChar('}');
546 return;
549 // identifier
550 if (par.eot || !par.isGoodIdChar(par.curch)) par.error("invalid identifier");
551 while (!par.eot && par.isGoodIdChar(par.curch)) par.skipChar();
554 void unserData(T) (out T v) {
555 if (par.eot) par.error("data expected");
556 static if (is(T : const(char)[])) {
557 // quoted string
558 static if (__traits(isStaticArray, T)) {
559 usize dpos = 0;
560 void put (char ch) {
561 if (v.length-dpos < 1) par.error("value too long");
562 v.ptr[dpos++] = ch;
564 } else {
565 void put (char ch) { v ~= ch; }
567 par.skipBlanks();
568 // `null` is empty string
569 if (par.curch != '"' && par.curch != '\'') {
570 if (!par.isGoodIdChar(par.curch)) par.error("string expected");
571 char[] ss;
572 while (par.isGoodIdChar(par.curch)) {
573 ss ~= par.curch;
574 par.skipChar();
576 if (ss != "null") foreach (char ch; ss) put(ch);
577 } else {
578 // not a null
579 assert(par.curch == '"' || par.curch == '\'');
580 par.parseString!true(&put);
582 } else static if (is(T : V[], V)) {
583 // array
584 par.skipBlanks();
585 if (par.curch == '{') {
586 // only one element
587 static if (__traits(isStaticArray, T)) {
588 if (v.length == 0) par.error("array too small");
589 } else {
590 v.length += 1;
592 unserData(v[0]);
593 } else if (par.curch == 'n') {
594 // this should be 'null'
595 par.skipChar(); if (!par.eot && par.curch != 'u') par.error("'null' expected");
596 par.skipChar(); if (!par.eot && par.curch != 'l') par.error("'null' expected");
597 par.skipChar(); if (!par.eot && par.curch != 'l') par.error("'null' expected");
598 par.skipChar(); if (!par.eot && par.isGoodIdChar(par.curch)) par.error("'null' expected");
599 static if (__traits(isStaticArray, T)) if (v.length != 0) par.error("static array too big");
600 } else {
601 par.expectChar('[');
602 static if (__traits(isStaticArray, T)) {
603 foreach (ref it; v) {
604 par.skipBlanks();
605 if (par.eot || par.curch == ']') break;
606 unserData(it);
607 skipComma();
609 } else {
610 for (;;) {
611 par.skipBlanks();
612 if (par.eot || par.curch == ']') break;
613 v.length += 1;
614 unserData(v[$-1]);
615 skipComma();
618 par.expectChar(']');
620 } else static if (is(T : V[K], K, V)) {
621 // associative array
622 K key = void;
623 V value = void;
624 par.expectChar('{');
625 for (;;) {
626 par.skipBlanks();
627 if (par.eot || par.curch == '}') break;
628 unserData(key);
629 par.expectChar(':');
630 par.skipBlanks();
631 // `null`?
632 if (par.curch == 'n' && par.peek == 'u') {
633 par.skipChar(); // skip 'n'
634 par.skipChar(); if (!par.eot && par.curch != 'l') par.error("'null' expected");
635 par.skipChar(); if (!par.eot && par.curch != 'l') par.error("'null' expected");
636 par.skipChar(); if (!par.eot && par.isGoodIdChar(par.curch)) par.error("'null' expected");
637 continue; // skip null value
638 } else {
639 unserData(value);
641 skipComma();
642 v[key] = value;
644 par.expectChar('}');
645 } else static if (isCharType!T) {
646 import std.conv : to;
647 auto id = par.expectId;
648 try {
649 v = id.to!uint.to!T;
650 } catch (Exception e) {
651 par.error("type conversion error for type '"~T.stringof~"' ("~id.idup~")");
653 } else static if (isSimpleType!T) {
654 import std.conv : to;
655 auto id = par.expectId;
656 // try bool->int conversions
657 static if ((is(T : ulong) || is(T : real)) && is(typeof((){v=0;})) && is(typeof((){v=1;}))) {
658 // char, int, etc.
659 if (id == "true") { v = 1; return; }
660 if (id == "false") { v = 0; return; }
662 try {
663 v = id.to!T;
664 } catch (Exception e) {
665 par.error("type conversion error for type '"~T.stringof~"' ("~id.idup~")");
667 } else static if (is(T == struct)) {
668 // struct
669 import std.traits : FieldNameTuple, getUDAs, hasUDA;
671 par.skipBlanks();
672 if (par.curch != '{') {
673 auto nm = par.expectId!true();
674 if (nm != (Unqual!T).stringof) par.error("'"~(Unqual!T).stringof~"' struct expected, but got '"~nm.idup~"'");
675 par.expectChar(':');
677 par.expectChar('{');
679 ulong[(FieldNameTuple!T.length+ulong.sizeof-1)/ulong.sizeof] fldseen = 0;
681 bool tryField(uint idx, string fldname) (const(char)[] name) {
682 static if (hasUDA!(__traits(getMember, T, fldname), SRZName)) {
683 enum names = getUDAs!(__traits(getMember, T, fldname), SRZName);
684 } else {
685 alias tuple(T...) = T;
686 enum names = tuple!(SRZName(fldname));
688 foreach (immutable xname; names) {
689 if (xname.name == name) {
690 if (fldseen[idx/8]&(1UL<<(idx%8))) throw new Exception(`duplicate field value for '`~fldname~`'`);
691 fldseen[idx/8] |= 1UL<<(idx%8);
692 unserData(__traits(getMember, v, fldname));
693 return true;
696 return false;
699 void tryAllFields (const(char)[] name) {
700 foreach (immutable idx, string fldname; FieldNameTuple!T) {
701 static if (!hasUDA!(__traits(getMember, T, fldname), SRZIgnore)) {
702 if (tryField!(idx, fldname)(name)) return;
705 static if (ignoreUnknown) {
706 skipData!false();
707 } else {
708 throw new Exception("unknown field '"~name.idup~"'");
712 // let's hope that fields are in order (it is nothing wrong with seeing 'em in wrong order, though)
713 static if (ignoreUnknown) {
714 while (par.curch != '}') {
715 if (par.curch == 0) break;
716 auto name = par.expectId!true();
717 par.expectChar(':');
718 tryAllFields(name);
719 skipComma();
721 } else {
722 foreach (immutable idx, string fldname; FieldNameTuple!T) {
723 static if (!hasUDA!(__traits(getMember, T, fldname), SRZIgnore)) {
724 par.skipBlanks();
725 if (par.curch == '}') break;
726 auto name = par.expectId!true();
727 par.expectChar(':');
728 if (!tryField!(idx, fldname)(name)) tryAllFields(name);
729 skipComma();
734 par.expectChar('}');
738 unserData(v);
742 // ////////////////////////////////////////////////////////////////////////// //
743 private static bool isValidDC (dchar c) pure nothrow @safe @nogc { pragma(inline, true); return (c < 0xD800 || (c > 0xDFFF && c <= 0x10FFFF)); } /// is given codepoint valid?
745 /// returns -1 on error (out of room in `s`, for example), or bytes taken
746 private int utf8Encode(dchar replacement='\uFFFD') (char[] s, dchar c) pure nothrow @trusted @nogc {
747 static assert(isValidDC(replacement), "invalid replacement char");
748 if (!isValidDC(c)) c = replacement;
749 if (c <= 0x7F) {
750 if (s.length < 1) return -1;
751 s.ptr[0] = cast(char)c;
752 return 1;
753 } else {
754 char[4] buf;
755 ubyte len;
756 if (c <= 0x7FF) {
757 buf.ptr[0] = cast(char)(0xC0|(c>>6));
758 buf.ptr[1] = cast(char)(0x80|(c&0x3F));
759 len = 2;
760 } else if (c <= 0xFFFF) {
761 buf.ptr[0] = cast(char)(0xE0|(c>>12));
762 buf.ptr[1] = cast(char)(0x80|((c>>6)&0x3F));
763 buf.ptr[2] = cast(char)(0x80|(c&0x3F));
764 len = 3;
765 } else if (c <= 0x10FFFF) {
766 buf.ptr[0] = cast(char)(0xF0|(c>>18));
767 buf.ptr[1] = cast(char)(0x80|((c>>12)&0x3F));
768 buf.ptr[2] = cast(char)(0x80|((c>>6)&0x3F));
769 buf.ptr[3] = cast(char)(0x80|(c&0x3F));
770 len = 4;
771 } else {
772 assert(0, "wtf?!");
774 if (s.length < len) return -1;
775 s[0..len] = buf[0..len];
776 return len;
781 // ////////////////////////////////////////////////////////////////////////// //
782 version(egserial_test) unittest {
783 version(no_vfs) import std.stdio; else { import iv.vfs.io; import iv.vfs.streams; }
785 version(no_vfs) static struct InRng {
786 char[] src;
787 @property char front () { return src[0]; }
788 @property bool empty () { return (src.length == 0); }
789 void popFront () { src = src[1..$]; }
792 char[] s2s(T) (in auto ref T v) {
793 version(no_vfs) {
794 char[] res;
795 static struct OutRng {
796 char[]* dest;
797 //void put (const(char)[] ch...) { *dest ~= ch; }
798 void put (char ch) { *dest ~= ch; }
800 auto or = OutRng(&res);
801 v.txtser(or);
802 return res;
803 } else {
804 ubyte[] res;
805 auto buf = MemoryStreamRWRef(res);
807 auto fl = wrapStream(buf);
808 v.txtser(fl);
810 return cast(char[])*buf.bytes;
814 // ////////////////////////////////////////////////////////////////////////// //
815 static struct AssemblyInfo {
816 uint id;
817 string name;
818 @SRZIgnore uint ignoreme;
821 static struct ReplyAsmInfo {
822 @SRZName("command") @SRZName("xcommand") ubyte cmd;
823 @SRZName("values") AssemblyInfo[][2] list;
824 uint[string] dict;
825 @SRZNonDefaultOnly bool fbool;
826 char[3] ext;
830 // ////////////////////////////////////////////////////////////////////////// //
831 void test0 () {
832 ReplyAsmInfo ri;
833 ri.cmd = 42;
834 ri.list[0] ~= AssemblyInfo(665, "limbo");
835 ri.list[1] ~= AssemblyInfo(69, "pleasure");
836 ri.dict["foo"] = 42;
837 ri.dict["boo"] = 665;
838 //ri.fbool = true;
839 ri.ext = "elf";
840 auto srs = s2s(ri);
841 writeln(srs);
843 ReplyAsmInfo xf;
844 version(no_vfs) {
845 xf.txtunser(InRng(srs));
846 } else {
847 xf.txtunser(wrapMemoryRO(srs));
849 //assert(fl.tell == fl.size);
850 assert(xf.cmd == 42);
851 assert(xf.list.length == 2);
852 assert(xf.list[0].length == 1);
853 assert(xf.list[1].length == 1);
854 assert(xf.list[0][0].id == 665);
855 assert(xf.list[0][0].name == "limbo");
856 assert(xf.list[1][0].id == 69);
857 assert(xf.list[1][0].name == "pleasure");
858 assert(xf.dict.length == 2);
859 assert(xf.dict["foo"] == 42);
860 assert(xf.dict["boo"] == 665);
861 //assert(xf.fbool == true);
862 assert(xf.fbool == false);
863 assert(xf.ext == "elf");
867 /*void main ()*/ {
868 test0();
869 write("string: ");
870 "Alice".txtser(stdout);
871 writeln;
875 import std.utf : byChar;
876 static struct Boo {
877 int n = -1;
879 Boo boo;
880 boo.txtunser("{ n:true }".byChar);
881 writeln(boo.n);
885 long[94] n;
886 foreach (immutable idx, ref v; n) v = idx;
887 n.txtser(stdout);