blendish: cosmetix
[iv.d.git] / txtser.d
blob4ac32619357383d5fc91321dc80322ad09aaed8f
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, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 /// very simple (de)serializer to json-like text format
18 module iv.txtser /*is aliced*/;
19 private:
21 import std.range : ElementEncodingType, isInputRange, isOutputRange;
22 import std.traits : Unqual;
23 import iv.alice;
24 import iv.vfs;
27 // ////////////////////////////////////////////////////////////////////////// //
28 public enum SRZIgnore; /// ignore this field
29 public struct SRZName { string name; } /// rename this field
30 public enum SRZNonDefaultOnly; /// write only if it has non-default value
33 // ////////////////////////////////////////////////////////////////////////// //
34 template arrayElementType(T) {
35 private import std.traits : isArray, Unqual;
36 static if (isArray!T) {
37 alias arrayElementType = arrayElementType!(typeof(T.init[0]));
38 } else static if (is(typeof(T))) {
39 alias arrayElementType = Unqual!(typeof(T));
40 } else {
41 alias arrayElementType = Unqual!T;
44 static assert(is(arrayElementType!string == char));
46 template isSimpleType(T) {
47 private import std.traits : Unqual;
48 private alias UT = Unqual!T;
49 enum isSimpleType = __traits(isIntegral, UT) || __traits(isFloating, UT) || is(UT == bool);
52 template isCharType(T) {
53 private import std.traits : Unqual;
54 private alias UT = Unqual!T;
55 enum isCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar);
59 // ////////////////////////////////////////////////////////////////////////// //
60 ///
61 public void txtser(T, ST) (in auto ref T v, auto ref ST fl, int indent=0, bool skipstname=false)
62 if (!is(T == class) && (isWriteableStream!ST || isOutputRange!(ST, char)))
64 enum Indent = 2;
66 void xput (const(char)[] s...) {
67 if (s.length == 0) return;
68 static if (isWriteableStream!ST) {
69 fl.rawWrite(s[]);
70 } else {
71 static if (is(typeof(fl.put(s)))) {
72 fl.put(s);
73 } else {
74 foreach (char ch; s) fl.put(ch);
79 void quote (const(char)[] s) {
80 static immutable string hexd = "0123456789abcdef";
81 xput('"');
82 bool goodString = true;
83 foreach (char ch; s) if (ch < ' ' || ch == 127 || ch == '"' || ch == '\\') { goodString = false; break; }
84 if (goodString) {
85 // easy deal
86 xput(s);
87 } else {
88 // hard time
89 usize pos = 0;
90 while (pos < s.length) {
91 auto epos = pos;
92 while (epos < s.length) {
93 auto ch = s.ptr[epos];
94 if (ch < ' ' || ch == 127 || ch == '"' || ch == '\\') break;
95 ++epos;
97 if (epos > pos) {
98 xput(s[pos..epos]);
99 pos = epos;
101 if (pos < s.length) {
102 auto ch = s.ptr[pos++];
103 if (ch < ' ' || ch == 127 || ch == '"' || ch == '\\') {
104 switch (ch) {
105 case '\x1b': xput(`\e`); break;
106 case '\r': xput(`\r`); break;
107 case '\n': xput(`\n`); break;
108 case '\t': xput(`\t`); break;
109 case '"': case '\\': xput(`\`); xput(ch); break;
110 default:
111 xput(`\x`);
112 xput(hexd[(ch>>4)&0x0f]);
113 xput(hexd[ch&0x0f]);
114 break;
116 } else {
117 xput(ch);
122 xput('"');
125 void newline () {
126 xput('\n');
127 foreach (immutable _; 0..indent) xput(' ');
130 void serData(T) (in ref T v, bool skipstructname) {
131 alias UT = arrayElementType!T;
132 static if (is(T : const(char)[])) {
133 // string
134 static if (is(T == string) || is(T == const(char)[])) {
135 if (v.ptr is null) xput("null"); else quote(v);
136 } else {
137 quote(v);
139 } else static if (is(T : V[], V)) {
140 // array
141 if (v.length) {
142 xput("[");
143 indent += Indent;
144 foreach (immutable idx, const ref it; v) {
145 if (idx == 0 || (v.length >= 56 && idx%42 == 41)) newline();
146 serData(it, true);
147 if (idx != v.length-1) xput(",");
149 indent -= Indent;
150 newline;
151 xput("]");
152 } else {
153 xput("[]");
155 } else static if (is(T : V[K], K, V)) {
156 // associative array
157 if (v.length) {
158 xput("{");
159 indent += Indent;
160 auto len = v.length;
161 foreach (const kv; v.byKeyValue) {
162 newline;
163 serData(kv.key, true);
164 xput(": ");
165 serData(kv.value, true);
166 if (--len) xput(",");
168 indent -= Indent;
169 newline;
170 xput("}");
171 } else {
172 xput("{}");
174 } else static if (isCharType!UT) {
175 import std.conv : to;
176 xput((cast(uint)v).to!string);
177 } else static if (is(UT == enum)) {
178 bool enumFound = false;
179 foreach (string fldname; __traits(allMembers, UT)) {
180 if (v == __traits(getMember, UT, fldname)) {
181 //xput(UT.stringof);
182 //xput(".");
183 xput(fldname);
184 enumFound = true;
185 break;
188 if (!enumFound) {
189 import std.conv : to;
190 xput(v.to!string);
192 } else static if (isSimpleType!UT) {
193 import std.conv : to;
194 xput(v.to!string);
195 } else static if (is(UT == struct)) {
196 import std.traits : FieldNameTuple, getUDAs, hasUDA;
197 if (skipstructname) {
198 xput("{");
199 } else {
200 xput(UT.stringof);
201 xput(": {");
203 indent += Indent;
204 bool needComma = false;
205 foreach (string fldname; FieldNameTuple!UT) {
206 static if (!hasUDA!(__traits(getMember, UT, fldname), SRZIgnore)) {
207 enum names = getUDAs!(__traits(getMember, UT, fldname), SRZName);
208 static if (names.length) enum xname = names[0].name; else enum xname = fldname;
209 static assert(xname.length <= 255, "struct '"~UT.stringof~"': field name too long: "~xname);
210 static if (hasUDA!(__traits(getMember, UT, fldname), SRZNonDefaultOnly)) {
211 if (__traits(getMember, v, fldname) == __traits(getMember, v, fldname).init) continue;
213 if (needComma) xput(",");
214 newline;
215 xput(xname);
216 xput(": ");
217 serData(__traits(getMember, v, fldname), true);
218 needComma = true;
221 indent -= Indent;
222 newline;
223 xput("}");
224 } else {
225 static assert(0, "can't serialize type '"~T.stringof~"'");
229 serData(v, skipstname);
233 // ////////////////////////////////////////////////////////////////////////// //
235 public enum isGoodSerParser(T) = is(typeof((inout int=0) {
236 auto t = T.init;
237 char ch = t.curch;
238 ch = t.peek;
239 t.skipChar();
240 bool b = t.eot;
241 t.skipBlanks();
242 int l = t.line;
243 int c = t.col;
244 const(char)[] s = t.expectId!true();
245 s = t.expectId!false;
246 b = T.isGoodIdChar(' ');
247 t.expectChar('!');
248 int d = T.digitInBase('1', 10);
249 t.error("message");
250 t.parseString!true(delegate (char ch) {});
251 t.parseString!false(delegate (char ch) {});
252 }));
256 public struct TxtSerParser(ST) if (isReadableStream!ST || (isInputRange!ST && is(Unqual!(ElementEncodingType!ST) == char))) {
257 private:
258 ST st;
259 int eotflag; // 0: not; 1: at peek; -1: at front; -2: done
260 // buffer for identifier reading
261 char[128] buf = 0;
262 int bpos = 0;
263 static if (isReadableStream!ST) {
264 enum AsStream = true;
265 char[256] rdbuf = 0;
266 uint rdpos, rdused;
267 } else {
268 enum AsStream = false;
271 public:
272 int line, col;
273 char curch, peek;
275 public:
276 this() (auto ref ST stream) {
277 st = stream;
278 // load first chars
279 skipChar();
280 skipChar();
281 line = 1;
282 col = 1;
285 void error (string msg) { import std.conv : to; throw new Exception(msg~" around line "~line.to!string~", column "~col.to!string); }
287 @property bool eot () const pure nothrow @safe @nogc { pragma(inline, true); return (eotflag < -1); }
289 void skipChar () {
290 if (eotflag < 0) { curch = peek = 0; eotflag = -2; return; }
291 if (curch == '\n') { ++line; col = 1; } else ++col;
292 curch = peek;
293 if (eotflag > 0) { eotflag = -1; peek = 0; return; }
294 // read next char to `peek`
295 static if (AsStream) {
296 if (rdpos >= rdused) {
297 auto read = st.rawRead(rdbuf[]);
298 if (read.length == 0) {
299 peek = 0;
300 eotflag = 1;
301 return;
303 rdpos = 0;
304 rdused = cast(uint)read.length;
306 assert(rdpos < rdused);
307 peek = rdbuf.ptr[rdpos++];
308 } else {
309 if (!st.empty) {
310 peek = st.front;
311 st.popFront;
312 } else {
313 peek = 0;
314 eotflag = 1;
319 void skipBlanks () {
320 while (!eot) {
321 if ((curch == '/' && peek == '/') || curch == '#') {
322 while (!eot && curch != '\n') skipChar();
323 } else if (curch == '/' && peek == '*') {
324 skipChar();
325 skipChar();
326 while (!eot) {
327 if (curch == '*' && peek == '/') {
328 skipChar();
329 skipChar();
330 break;
333 } else if (curch == '/' && peek == '+') {
334 skipChar();
335 skipChar();
336 int level = 1;
337 while (!eot) {
338 if (curch == '+' && peek == '/') {
339 skipChar();
340 skipChar();
341 if (--level == 0) break;
342 } else if (curch == '/' && peek == '+') {
343 skipChar();
344 skipChar();
345 ++level;
348 } else if (curch > ' ') {
349 break;
350 } else {
351 skipChar();
356 void expectChar (char ch) {
357 skipBlanks();
358 if (eot || curch != ch) error("'"~ch~"' expected");
359 skipChar();
362 const(char)[] expectId(bool allowQuoted=false) () {
363 bpos = 0;
364 skipBlanks();
365 static if (allowQuoted) {
366 if (!eot && curch == '"') {
367 skipChar();
368 while (!eot && curch != '"') {
369 if (curch == '\\') error("simple string expected");
370 if (bpos >= buf.length) error("identifier or number too long");
371 buf.ptr[bpos++] = curch;
372 skipChar();
374 if (eot || curch != '"') error("simple string expected");
375 skipChar();
376 return buf[0..bpos];
379 if (!isGoodIdChar(curch)) error("identifier or number expected");
380 while (isGoodIdChar(curch)) {
381 if (bpos >= buf.length) error("identifier or number too long");
382 buf[bpos++] = curch;
383 skipChar();
385 return buf[0..bpos];
388 // `curch` is opening quote
389 void parseString(bool allowEscapes) (scope void delegate (char ch) put) {
390 assert(put !is null);
391 if (eot) error("unterminated string");
392 char qch = curch;
393 skipChar();
394 while (!eot && curch != qch) {
395 static if (allowEscapes) if (curch == '\\') {
396 skipChar();
397 if (eot) error("unterminated string");
398 switch (curch) {
399 case '0': // oops, octal
400 uint ucc = 0;
401 foreach (immutable _; 0..4) {
402 if (eot) error("unterminated string");
403 int dig = digitInBase(curch, 10);
404 if (dig < 0) break;
405 if (dig > 7) error("invalid octal escape");
406 ucc = ucc*8+dig;
407 skipChar();
409 if (ucc > 255) error("invalid octal escape");
410 put(cast(char)ucc);
411 break;
412 case '1': .. case '9': // decimal
413 uint ucc = 0;
414 foreach (immutable _; 0..3) {
415 if (eot) error("unterminated string");
416 int dig = digitInBase(curch, 10);
417 if (dig < 0) break;
418 ucc = ucc*10+dig;
419 skipChar();
421 if (ucc > 255) error("invalid decimal escape");
422 put(cast(char)ucc);
423 break;
424 case 'e': put('\x1b'); skipChar(); break;
425 case 'r': put('\r'); skipChar(); break;
426 case 'n': put('\n'); skipChar(); break;
427 case 't': put('\t'); skipChar(); break;
428 case '"': case '\\': case '\'': put(curch); skipChar(); break;
429 case 'x':
430 case 'X':
431 if (eot) error("unterminated string");
432 if (digitInBase(peek, 16) < 0) error("invalid hex escape");
433 skipChar(); // skip 'x'
434 if (eot) error("unterminated string");
435 if (digitInBase(curch, 16) < 0 || digitInBase(peek, 16) < 0) error("invalid hex escape");
436 put(cast(char)(digitInBase(curch, 16)*16+digitInBase(peek, 16)));
437 skipChar();
438 skipChar();
439 break;
440 case 'u':
441 case 'U':
442 if (digitInBase(peek, 16) < 0) error("invalid unicode escape");
443 uint ucc = 0;
444 skipChar(); // skip 'u'
445 foreach (immutable _; 0..4) {
446 if (eot) error("unterminated string");
447 if (digitInBase(curch, 16) < 0) break;
448 ucc = ucc*16+digitInBase(curch, 16);
449 skipChar();
451 char[4] buf = void;
452 auto len = utf8Encode(buf[], cast(dchar)ucc);
453 assert(len != 0);
454 if (len < 0) error("invalid utf-8 escape");
455 foreach (char ch; buf[0..len]) put(ch);
456 break;
457 default: error("invalid escape");
459 continue;
461 // normal char
462 put(curch);
463 skipChar();
465 if (eot || curch != qch) error("unterminated string");
466 skipChar();
469 static pure nothrow @safe @nogc:
470 bool isGoodIdChar (char ch) {
471 pragma(inline, true);
472 return
473 (ch >= '0' && ch <= '9') ||
474 (ch >= 'A' && ch <= 'Z') ||
475 (ch >= 'a' && ch <= 'z') ||
476 ch == '_' || ch == '-' || ch == '+' || ch == '.';
479 int digitInBase (char ch, int base) {
480 pragma(inline, true);
481 return
482 base >= 1 && ch >= '0' && ch < '0'+base ? ch-'0' :
483 base > 10 && ch >= 'A' && ch < 'A'+base-10 ? ch-'A'+10 :
484 base > 10 && ch >= 'a' && ch < 'a'+base-10 ? ch-'a'+10 :
490 static assert(isGoodSerParser!(TxtSerParser!VFile));
493 // ////////////////////////////////////////////////////////////////////////// //
495 public void txtunser(bool ignoreUnknown=false, T, ST) (out T v, auto ref ST fl)
496 if (!is(T == class) && (isReadableStream!ST || (isInputRange!ST && is(Unqual!(ElementEncodingType!ST) == char))))
498 auto par = TxtSerParser!ST(fl);
499 txtunser!ignoreUnknown(v, par);
504 public void txtunser(bool ignoreUnknown=false, T, ST) (out T v, auto ref ST par) if (!is(T == class) && isGoodSerParser!ST) {
505 import std.traits : Unqual;
507 void skipComma () {
508 par.skipBlanks();
509 if (par.curch == ',') par.skipChar();
512 static if (ignoreUnknown) void skipData(bool fieldname) () {
513 par.skipBlanks();
514 // string?
515 if (par.curch == '"' || par.curch == '\'') {
516 par.parseString!(!fieldname)(delegate (char ch) {});
517 return;
519 static if (!fieldname) {
520 // array?
521 if (par.curch == '[') {
522 par.skipChar();
523 for (;;) {
524 par.skipBlanks();
525 if (par.eot) par.error("unterminated array");
526 if (par.curch == ']') break;
527 skipData!false();
528 skipComma();
530 par.expectChar(']');
531 return;
533 // dictionary?
534 if (par.curch == '{') {
535 par.skipChar();
536 for (;;) {
537 par.skipBlanks();
538 if (par.eot) par.error("unterminated array");
539 if (par.curch == '}') break;
540 skipData!true(); // field name
541 par.skipBlanks();
542 par.expectChar(':');
543 skipData!false();
544 skipComma();
546 par.expectChar('}');
547 return;
550 // identifier
551 if (par.eot || !par.isGoodIdChar(par.curch)) par.error("invalid identifier");
552 while (!par.eot && par.isGoodIdChar(par.curch)) par.skipChar();
555 void unserData(T) (out T v) {
556 if (par.eot) par.error("data expected");
557 static if (is(T : const(char)[])) {
558 // quoted string
559 static if (__traits(isStaticArray, T)) {
560 usize dpos = 0;
561 void put (char ch) {
562 if (v.length-dpos < 1) par.error("value too long");
563 v.ptr[dpos++] = ch;
565 } else {
566 void put (char ch) { v ~= ch; }
568 par.skipBlanks();
569 // `null` is empty string
570 if (par.curch != '"' && par.curch != '\'') {
571 if (!par.isGoodIdChar(par.curch)) par.error("string expected");
572 char[] ss;
573 while (par.isGoodIdChar(par.curch)) {
574 ss ~= par.curch;
575 par.skipChar();
577 if (ss != "null") foreach (char ch; ss) put(ch);
578 } else {
579 // not a null
580 assert(par.curch == '"' || par.curch == '\'');
581 par.parseString!true(&put);
583 } else static if (is(T : V[], V)) {
584 // array
585 par.skipBlanks();
586 if (par.curch == '{') {
587 // only one element
588 static if (__traits(isStaticArray, T)) {
589 if (v.length == 0) par.error("array too small");
590 } else {
591 v.length += 1;
593 unserData(v[0]);
594 } else if (par.curch == 'n') {
595 // this should be 'null'
596 par.skipChar(); if (!par.eot && par.curch != 'u') par.error("'null' expected");
597 par.skipChar(); if (!par.eot && par.curch != 'l') par.error("'null' expected");
598 par.skipChar(); if (!par.eot && par.curch != 'l') par.error("'null' expected");
599 par.skipChar(); if (!par.eot && par.isGoodIdChar(par.curch)) par.error("'null' expected");
600 static if (__traits(isStaticArray, T)) if (v.length != 0) par.error("static array too big");
601 } else {
602 par.expectChar('[');
603 static if (__traits(isStaticArray, T)) {
604 foreach (ref it; v) {
605 par.skipBlanks();
606 if (par.eot || par.curch == ']') break;
607 unserData(it);
608 skipComma();
610 } else {
611 for (;;) {
612 par.skipBlanks();
613 if (par.eot || par.curch == ']') break;
614 v.length += 1;
615 unserData(v[$-1]);
616 skipComma();
619 par.expectChar(']');
621 } else static if (is(T : V[K], K, V)) {
622 // associative array
623 K key = void;
624 V value = void;
625 par.expectChar('{');
626 for (;;) {
627 par.skipBlanks();
628 if (par.eot || par.curch == '}') break;
629 unserData(key);
630 par.expectChar(':');
631 par.skipBlanks();
632 // `null`?
633 if (par.curch == 'n' && par.peek == 'u') {
634 par.skipChar(); // skip 'n'
635 par.skipChar(); if (!par.eot && par.curch != 'l') par.error("'null' expected");
636 par.skipChar(); if (!par.eot && par.curch != 'l') par.error("'null' expected");
637 par.skipChar(); if (!par.eot && par.isGoodIdChar(par.curch)) par.error("'null' expected");
638 continue; // skip null value
639 } else {
640 unserData(value);
642 skipComma();
643 v[key] = value;
645 par.expectChar('}');
646 } else static if (isCharType!T) {
647 import std.conv : to;
648 auto id = par.expectId;
649 try {
650 v = id.to!uint.to!T;
651 } catch (Exception e) {
652 par.error("type conversion error for type '"~T.stringof~"' ("~id.idup~")");
654 } else static if (isSimpleType!T) {
655 import std.conv : to;
656 auto id = par.expectId;
657 // try bool->int conversions
658 static if ((is(T : ulong) || is(T : real)) && is(typeof((){v=0;})) && is(typeof((){v=1;}))) {
659 // char, int, etc.
660 if (id == "true") { v = 1; return; }
661 if (id == "false") { v = 0; return; }
663 try {
664 v = id.to!T;
665 } catch (Exception e) {
666 par.error("type conversion error for type '"~T.stringof~"' ("~id.idup~")");
668 } else static if (is(T == struct)) {
669 // struct
670 import std.traits : FieldNameTuple, getUDAs, hasUDA;
672 par.skipBlanks();
673 if (par.curch != '{') {
674 auto nm = par.expectId!true();
675 if (nm != (Unqual!T).stringof) par.error("'"~(Unqual!T).stringof~"' struct expected, but got '"~nm.idup~"'");
676 par.expectChar(':');
678 par.expectChar('{');
680 ulong[(FieldNameTuple!T.length+ulong.sizeof-1)/ulong.sizeof] fldseen = 0;
682 bool tryField(uint idx, string fldname) (const(char)[] name) {
683 static if (hasUDA!(__traits(getMember, T, fldname), SRZName)) {
684 enum names = getUDAs!(__traits(getMember, T, fldname), SRZName);
685 } else {
686 alias tuple(T...) = T;
687 enum names = tuple!(SRZName(fldname));
689 foreach (immutable xname; names) {
690 if (xname.name == name) {
691 if (fldseen[idx/8]&(1UL<<(idx%8))) throw new Exception(`duplicate field value for '`~fldname~`'`);
692 fldseen[idx/8] |= 1UL<<(idx%8);
693 unserData(__traits(getMember, v, fldname));
694 return true;
697 return false;
700 void tryAllFields (const(char)[] name) {
701 foreach (immutable idx, string fldname; FieldNameTuple!T) {
702 static if (!hasUDA!(__traits(getMember, T, fldname), SRZIgnore)) {
703 if (tryField!(idx, fldname)(name)) return;
706 static if (ignoreUnknown) {
707 skipData!false();
708 } else {
709 throw new Exception("unknown field '"~name.idup~"'");
713 // let's hope that fields are in order (it is nothing wrong with seeing 'em in wrong order, though)
714 static if (ignoreUnknown) {
715 while (par.curch != '}') {
716 if (par.curch == 0) break;
717 auto name = par.expectId!true();
718 par.expectChar(':');
719 tryAllFields(name);
720 skipComma();
722 } else {
723 foreach (immutable idx, string fldname; FieldNameTuple!T) {
724 static if (!hasUDA!(__traits(getMember, T, fldname), SRZIgnore)) {
725 par.skipBlanks();
726 if (par.curch == '}') break;
727 auto name = par.expectId!true();
728 par.expectChar(':');
729 if (!tryField!(idx, fldname)(name)) tryAllFields(name);
730 skipComma();
735 par.expectChar('}');
739 unserData(v);
743 // ////////////////////////////////////////////////////////////////////////// //
744 private static bool isValidDC (dchar c) pure nothrow @safe @nogc { pragma(inline, true); return (c < 0xD800 || (c > 0xDFFF && c <= 0x10FFFF)); } /// is given codepoint valid?
746 /// returns -1 on error (out of room in `s`, for example), or bytes taken
747 private int utf8Encode(dchar replacement='\uFFFD') (char[] s, dchar c) pure nothrow @trusted @nogc {
748 static assert(isValidDC(replacement), "invalid replacement char");
749 if (!isValidDC(c)) c = replacement;
750 if (c <= 0x7F) {
751 if (s.length < 1) return -1;
752 s.ptr[0] = cast(char)c;
753 return 1;
754 } else {
755 char[4] buf;
756 ubyte len;
757 if (c <= 0x7FF) {
758 buf.ptr[0] = cast(char)(0xC0|(c>>6));
759 buf.ptr[1] = cast(char)(0x80|(c&0x3F));
760 len = 2;
761 } else if (c <= 0xFFFF) {
762 buf.ptr[0] = cast(char)(0xE0|(c>>12));
763 buf.ptr[1] = cast(char)(0x80|((c>>6)&0x3F));
764 buf.ptr[2] = cast(char)(0x80|(c&0x3F));
765 len = 3;
766 } else if (c <= 0x10FFFF) {
767 buf.ptr[0] = cast(char)(0xF0|(c>>18));
768 buf.ptr[1] = cast(char)(0x80|((c>>12)&0x3F));
769 buf.ptr[2] = cast(char)(0x80|((c>>6)&0x3F));
770 buf.ptr[3] = cast(char)(0x80|(c&0x3F));
771 len = 4;
772 } else {
773 assert(0, "wtf?!");
775 if (s.length < len) return -1;
776 s[0..len] = buf[0..len];
777 return len;
782 // ////////////////////////////////////////////////////////////////////////// //
783 version(egserial_test) unittest {
784 version(no_vfs) import std.stdio; else { import iv.vfs.io; import iv.vfs.streams; }
786 version(no_vfs) static struct InRng {
787 char[] src;
788 @property char front () { return src[0]; }
789 @property bool empty () { return (src.length == 0); }
790 void popFront () { src = src[1..$]; }
793 char[] s2s(T) (in auto ref T v) {
794 version(no_vfs) {
795 char[] res;
796 static struct OutRng {
797 char[]* dest;
798 //void put (const(char)[] ch...) { *dest ~= ch; }
799 void put (char ch) { *dest ~= ch; }
801 auto or = OutRng(&res);
802 v.txtser(or);
803 return res;
804 } else {
805 ubyte[] res;
806 auto buf = MemoryStreamRWRef(res);
808 auto fl = wrapStream(buf);
809 v.txtser(fl);
811 return cast(char[])*buf.bytes;
815 // ////////////////////////////////////////////////////////////////////////// //
816 static struct AssemblyInfo {
817 uint id;
818 string name;
819 @SRZIgnore uint ignoreme;
822 static struct ReplyAsmInfo {
823 @SRZName("command") @SRZName("xcommand") ubyte cmd;
824 @SRZName("values") AssemblyInfo[][2] list;
825 uint[string] dict;
826 @SRZNonDefaultOnly bool fbool;
827 char[3] ext;
831 // ////////////////////////////////////////////////////////////////////////// //
832 void test0 () {
833 ReplyAsmInfo ri;
834 ri.cmd = 42;
835 ri.list[0] ~= AssemblyInfo(665, "limbo");
836 ri.list[1] ~= AssemblyInfo(69, "pleasure");
837 ri.dict["foo"] = 42;
838 ri.dict["boo"] = 665;
839 //ri.fbool = true;
840 ri.ext = "elf";
841 auto srs = s2s(ri);
842 writeln(srs);
844 ReplyAsmInfo xf;
845 version(no_vfs) {
846 xf.txtunser(InRng(srs));
847 } else {
848 xf.txtunser(wrapMemoryRO(srs));
850 //assert(fl.tell == fl.size);
851 assert(xf.cmd == 42);
852 assert(xf.list.length == 2);
853 assert(xf.list[0].length == 1);
854 assert(xf.list[1].length == 1);
855 assert(xf.list[0][0].id == 665);
856 assert(xf.list[0][0].name == "limbo");
857 assert(xf.list[1][0].id == 69);
858 assert(xf.list[1][0].name == "pleasure");
859 assert(xf.dict.length == 2);
860 assert(xf.dict["foo"] == 42);
861 assert(xf.dict["boo"] == 665);
862 //assert(xf.fbool == true);
863 assert(xf.fbool == false);
864 assert(xf.ext == "elf");
868 /*void main ()*/ {
869 test0();
870 write("string: ");
871 "Alice".txtser(stdout);
872 writeln;
876 import std.utf : byChar;
877 static struct Boo {
878 int n = -1;
880 Boo boo;
881 boo.txtunser("{ n:true }".byChar);
882 writeln(boo.n);
886 long[94] n;
887 foreach (immutable idx, ref v; n) v = idx;
888 n.txtser(stdout);