Mercurial > projects > mde
comparison mde/file/deserialize.d @ 81:d8fccaa45d5f
Moved file IO code from mde/mergetag to mde/file[/mergetag] and changed how some errors are caught.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Fri, 29 Aug 2008 11:59:43 +0100 |
parents | mde/mergetag/deserialize.d@61ea26abe4dd |
children | ac1e3fd07275 |
comparison
equal
deleted
inserted
replaced
80:ea58f277f487 | 81:d8fccaa45d5f |
---|---|
1 /* LICENSE BLOCK | |
2 Part of mde: a Modular D game-oriented Engine | |
3 Copyright © 2007-2008 Diggory Hardy | |
4 | |
5 This program is free software: you can redistribute it and/or modify it under the terms | |
6 of the GNU General Public License as published by the Free Software Foundation, either | |
7 version 2 of the License, or (at your option) any later version. | |
8 | |
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | |
10 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
11 See the GNU General Public License for more details. | |
12 | |
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/>. */ | |
15 | |
16 /************************************************************************************************** | |
17 * Generic deserialization templated function. | |
18 * | |
19 * Supports: | |
20 * Associative arrays, arrays (inc. strings), structs, char types, bool, int types, float types. | |
21 * | |
22 * There are also some public utility functions with their own documentation. | |
23 * | |
24 * Examples: | |
25 * ------------------------------------------------------------------------------------------------ | |
26 * // Basic examples: | |
27 * ulong a = deserialize!(ulong) ("20350"); | |
28 * float d = deserialize!(float) (" 1.2e-9 "); | |
29 * int[] b = deserialize!(int[]) ("[0,1,2,3]"); | |
30 * | |
31 * // String and char[] syntax: | |
32 * char[] c = deserialize!(char[]) ("\"A string\""); | |
33 * char[] e = deserialize!(char[]) ("['a','n','o','t','h','e','r', ' ' ,'s','t','r','i','n','g']"); | |
34 * | |
35 * // These be used interchangably; here's a more complex example of an associative array: | |
36 * bool[char[]] f = deserialize!(bool[char[]]) ("[ \"one\":true, ['t','w','o']:false, \"three\":1, \"four\":000 ]"); | |
37 * | |
38 * // There is also a special notation for ubyte[] types: | |
39 * // The digits following 0x must be in pairs and each specify one ubyte. | |
40 * assert ( deserialize!(ubyte[]) (`0x01F2AC`) == deserialize!(ubyte[]) (`[01 ,0xF2, 0xAC]`) ); | |
41 * | |
42 * // There's no limit to the complexity! | |
43 * char[char[][][][char]][bool] z = ...; // don't expect me to write this! | |
44 * ------------------------------------------------------------------------------------------------ | |
45 * | |
46 * Throws: | |
47 * May throw a ParseException or a UnicodeException (which both extend TextException). | |
48 * | |
49 * TODO: Optimize memory allocation (if possible?). Test best sizes for initial allocations | |
50 * instead of merely guessing? | |
51 *************************************************************************************************/ | |
52 //NOTE: in case of multiple formats, make this a dummy module importing both serialize modules, | |
53 // or put all the code here. | |
54 module mde.file.deserialize; | |
55 | |
56 // tango imports | |
57 import tango.core.Exception : TextException, UnicodeException; | |
58 import cInt = tango.text.convert.Integer; | |
59 import cFloat = tango.text.convert.Float; | |
60 import Utf = tango.text.convert.Utf; | |
61 import Util = tango.text.Util; | |
62 | |
63 /** | |
64 * Base class for deserialize exceptions. | |
65 */ | |
66 class ParseException : TextException | |
67 { | |
68 this( char[] msg ) | |
69 { | |
70 super( msg ); | |
71 } | |
72 } | |
73 | |
74 alias deserialize parseTo; // support the old name | |
75 | |
76 //BEGIN deserialize templates | |
77 | |
78 // Associative arrays | |
79 | |
80 T[S] deserialize(T : T[S], S) (char[] src) { | |
81 src = Util.trim(src); | |
82 if (src.length < 2 || src[0] != '[' || src[$-1] != ']') | |
83 throw new ParseException ("Invalid associative array: not [ ... ]"); // bad braces. | |
84 | |
85 T[S] ret; | |
86 foreach (char[] pair; split (src[1..$-1])) { | |
87 uint i = 0; | |
88 while (i < pair.length) { // advance to the ':' | |
89 char c = pair[i]; | |
90 if (c == ':') break; | |
91 if (c == '\'' || c == '"') { // string or character | |
92 ++i; | |
93 while (i < pair.length && pair[i] != c) { | |
94 if (pair[i] == '\\') | |
95 ++i; // escape seq. | |
96 ++i; | |
97 } | |
98 // Could have an unterminated ' or " causing i >= pair.length, but: | |
99 // 1. Impossible: split would have thrown | |
100 // 2. In any case this would be caught below. | |
101 } | |
102 ++i; | |
103 } | |
104 if (i >= pair.length) | |
105 throw new ParseException ("Invalid associative array: encountered [ ... KEY] (missing :DATA)"); | |
106 ret[deserialize!(S) (pair[0..i])] = deserialize!(T) (pair[i+1..$]); | |
107 } | |
108 return ret; | |
109 } | |
110 | |
111 | |
112 // Arrays | |
113 | |
114 T[] deserialize(T : T[]) (char[] src) { | |
115 src = Util.trim(src); | |
116 if (src.length >= 2 && src[0] == '[' && src[$-1] == ']') | |
117 return toArray!(T[]) (src); | |
118 throw new ParseException ("Invalid array: not [ ... ]"); | |
119 } | |
120 | |
121 // String (array special case) | |
122 T deserialize(T : char[]) (char[] src) { | |
123 src = Util.trim(src); | |
124 if (src.length >= 2 && src[0] == '"' && src[$-1] == '"') { | |
125 src = src[1..$-1]; | |
126 T ret; | |
127 ret.length = src.length; // maximum length; retract to actual length later | |
128 uint i = 0; | |
129 for (uint t = 0; t < src.length;) { | |
130 // process a block of non-escaped characters | |
131 uint s = t; | |
132 while (t < src.length && src[t] != '\\') ++t; // non-escaped characters | |
133 uint j = i + t - s; | |
134 ret[i..j] = src[s..t]; // copy a block | |
135 i = j; | |
136 | |
137 // process a block of escaped characters | |
138 while (t < src.length && src[t] == '\\') { | |
139 t++; | |
140 if (t == src.length) | |
141 throw new ParseException ("Invalid string: ends \\\" !"); // next char is " | |
142 ret[i++] = unEscapeChar (src[t++]); // throws if it's invalid | |
143 } | |
144 } | |
145 return ret[0..i]; | |
146 } | |
147 else if (src.length >= 2 && src[0] == '[' && src[$-1] == ']') | |
148 return toArray!(T) (src); | |
149 throw new ParseException ("Invalid string: not quoted (\"*\") or char array (['a',...,'c'])"); | |
150 } | |
151 // Unicode conversions for strings: | |
152 T deserialize(T : wchar[]) (char[] src) { | |
153 // May throw a UnicodeException; don't bother catching and rethrowing: | |
154 return Utf.toString16 (deserialize!(char[]) (src)); | |
155 } | |
156 T deserialize(T : dchar[]) (char[] src) { | |
157 // May throw a UnicodeException; don't bother catching and rethrowing: | |
158 return Utf.toString32 (deserialize!(char[]) (src)); | |
159 } | |
160 | |
161 // Binary (array special case) | |
162 T deserialize(T : ubyte[]) (char[] src) { | |
163 src = Util.trim(src); | |
164 // Standard case: | |
165 if (src.length >= 2 && src[0] == '[' && src[$-1] == ']') return toArray!(T) (src); | |
166 // Special case: sequence of hex digits, each pair of which is a ubyte | |
167 if (src.length >= 2 && src[0..2] == "0x") { | |
168 src = src[2..$]; // strip down to actual digits | |
169 | |
170 // Must be in pairs: | |
171 if (src.length % 2 == 1) | |
172 throw new ParseException ("Invalid binary: odd number of chars"); | |
173 | |
174 T ret; | |
175 ret.length = src.length / 2; // exact | |
176 | |
177 for (uint i, pos; pos + 1 < src.length; ++i) { | |
178 ubyte x = readHexChar(src, pos) << 4; | |
179 x |= readHexChar(src, pos); | |
180 ret[i] = x; | |
181 } | |
182 return ret; | |
183 } | |
184 else throw new ParseException ("Invalid ubyte[]: not an array and doesn't start 0x"); | |
185 } | |
186 | |
187 | |
188 // Basic types | |
189 | |
190 // Char | |
191 // Assumes value is <= 127 (for valid UTF-8), since input would be invalid UTF-8 if not anyway. | |
192 // (And we're not really interested in checking for valid unicode; char[] conversions don't either.) | |
193 T deserialize(T : char) (char[] src) { | |
194 src = Util.trim(src); | |
195 if (src.length < 3 || src[0] != '\'' || src[$-1] != '\'') | |
196 throw new ParseException ("Invalid char: not 'x' or '\\x'"); | |
197 if (src[1] != '\\') { | |
198 if (src.length == 3) | |
199 return src[1]; // Either non escaped | |
200 throw new ParseException ("Invalid char: too long (or non-ASCII)"); | |
201 } else if (src.length == 4) | |
202 return unEscapeChar (src[2]); // Or escaped | |
203 | |
204 throw new ParseException ("Invalid char: '\\'"); | |
205 } | |
206 // Basic unicode convertions for wide-chars. | |
207 // Assumes value is <= 127 as does deserialize!(char). | |
208 T deserialize(T : wchar) (char[] src) { | |
209 return cast(T) deserialize!(char) (src); | |
210 } | |
211 T deserialize(T : dchar) (char[] src) { | |
212 return cast(T) deserialize!(char) (src); | |
213 } | |
214 | |
215 // Bool | |
216 T deserialize(T : bool) (char[] src) { | |
217 src = Util.trim(src); | |
218 if (src == "true") | |
219 return true; | |
220 if (src == "false") | |
221 return false; | |
222 uint pos; | |
223 while (src.length > pos && src[pos] == '0') ++pos; // skip leading zeros | |
224 if (src.length == pos && pos > 0) | |
225 return false; | |
226 if (src.length == pos + 1 && src[pos] == '1') | |
227 return true; | |
228 throw new ParseException ("Invalid bool: not true or false and doesn't evaluate to 0 or 1"); | |
229 } | |
230 | |
231 // Ints | |
232 T deserialize(T : byte) (char[] src) { | |
233 return toTInt!(T) (src); | |
234 } | |
235 T deserialize(T : short) (char[] src) { | |
236 return toTInt!(T) (src); | |
237 } | |
238 T deserialize(T : int) (char[] src) { | |
239 return toTInt!(T) (src); | |
240 } | |
241 T deserialize(T : long) (char[] src) { | |
242 return toTInt!(T) (src); | |
243 } | |
244 T deserialize(T : ubyte) (char[] src) { | |
245 return toTInt!(T) (src); | |
246 } | |
247 T deserialize(T : ushort) (char[] src) { | |
248 return toTInt!(T) (src); | |
249 } | |
250 T deserialize(T : uint) (char[] src) { | |
251 return toTInt!(T) (src); | |
252 } | |
253 T deserialize(T : ulong) (char[] src) { | |
254 return toTInt!(T) (src); | |
255 } | |
256 debug (UnitTest) unittest { | |
257 assert (deserialize!(byte) ("-5") == cast(byte) -5); | |
258 // annoyingly, octal syntax differs from D (blame tango): | |
259 assert (deserialize!(uint[]) ("[0b0100,0o724,0xFa59c,0xFFFFFFFF,0]") == [0b0100u,0724,0xFa59c,0xFFFFFFFF,0]); | |
260 } | |
261 | |
262 // Floats | |
263 T deserialize(T : float) (char[] src) { | |
264 return toTFloat!(T) (src); | |
265 } | |
266 T deserialize(T : double) (char[] src) { | |
267 return toTFloat!(T) (src); | |
268 } | |
269 T deserialize(T : real) (char[] src) { | |
270 return toTFloat!(T) (src); | |
271 } | |
272 | |
273 | |
274 // Structs | |
275 T deserialize(T) (char[] src) { | |
276 static assert (is(T == struct), "Unsupported type: "~typeof(T)); | |
277 | |
278 src = Util.trim(src); | |
279 if (src.length < 2 || src[0] != '{' || src[$-1] != '}') | |
280 throw new ParseException ("Invalid struct: not { ... }"); | |
281 | |
282 // cannot access elements of T.tupleof with non-const key, so use a type which can be | |
283 // accessed with a non-const key to store slices: | |
284 char[][T.tupleof.length] temp; | |
285 foreach (char[] pair; split (src[1..$-1])) { | |
286 uint i = 0; | |
287 while (i < pair.length) { // advance to the ':' | |
288 char c = pair[i]; | |
289 if (c == ':') | |
290 break; | |
291 // key must be an int so no need for string checks | |
292 ++i; | |
293 } | |
294 if (i >= pair.length) | |
295 throw new ParseException ("Invalid struct: encountered { ... KEY} (missing :DATA)"); | |
296 | |
297 size_t k = deserialize!(size_t) (pair[0..i]); | |
298 // Note: could check no entry was already stored in temp. | |
299 temp[k] = pair[i+1..$]; | |
300 } | |
301 T ret; | |
302 setStruct (ret, temp); | |
303 return ret; | |
304 } | |
305 //END deserialize templates | |
306 | |
307 //BEGIN Utility funcs | |
308 /** Splits a string into substrings separated by '$(B ,)' with support for characters and strings | |
309 * containing escape sequences and for embedded arrays ($(B [...])). | |
310 * | |
311 * Params: | |
312 * src A string to separate on commas. It shouldn't have enclosing brackets. | |
313 * | |
314 * Returns: | |
315 * An array of substrings within src, excluding commas. Whitespace is not stripped and | |
316 * empty strings may get returned. | |
317 * | |
318 * Remarks: | |
319 * This function is primarily intended for as a utility function for use by the templates | |
320 * parsing arrays and associative arrays, but it may be useful in other cases too. Hence the | |
321 * fact no brackets are stripped from src. | |
322 */ | |
323 //FIXME foreach struct is more efficient | |
324 char[][] split (char[] src) { | |
325 src = Util.trim (src); | |
326 if (src == "") | |
327 return []; // empty array: no elements when no data | |
328 | |
329 uint depth = 0; // surface depth (embedded arrays) | |
330 char[][] ret; | |
331 ret.length = src.length / 3; // unlikely to need a longer array | |
332 uint k = 0; // current split piece | |
333 uint i = 0, j = 0; // current read location, start of current piece | |
334 | |
335 while (i < src.length) { | |
336 char c = src[i]; | |
337 if (c == '\'' || c == '"') { // string or character | |
338 ++i; | |
339 while (i < src.length && src[i] != c) { | |
340 if (src[i] == '\\') | |
341 ++i; // escape seq. | |
342 ++i; | |
343 } // Doesn't throw if no terminal quote at end of src, but this should be caught later. | |
344 } | |
345 else if (c == '[') ++depth; | |
346 else if (c == ']') { | |
347 if (depth) | |
348 --depth; | |
349 else throw new ParseException ("Invalid array literal: closes before end of data item."); | |
350 } | |
351 else if (c == ',' && depth == 0) { // only if not an embedded array | |
352 if (ret.length <= k) | |
353 ret.length = ret.length * 2; | |
354 ret[k++] = src[j..i]; // add this piece and increment k | |
355 j = i + 1; | |
356 } | |
357 ++i; | |
358 } | |
359 if (i > src.length) | |
360 throw new ParseException ("Unterminated quote (\' or \")"); | |
361 | |
362 if (ret.length <= k) | |
363 ret.length = k + 1; | |
364 ret[k] = src[j..i]; // add final piece (i >= j) | |
365 return ret[0..k+1]; | |
366 } | |
367 | |
368 /* Templated read-int function to read (un)signed 1-4 byte integers. | |
369 * | |
370 * Actually a reimplementation of tango.text.convert.Integer toLong and parse functions. | |
371 */ | |
372 private TInt toTInt(TInt) (char[] src) { | |
373 const char[] INT_OUT_OF_RANGE = "Integer out of range"; | |
374 bool sign; | |
375 uint radix, ate, ate2; | |
376 | |
377 // Trim off whitespace. | |
378 // NOTE: Cannot use tango.text.convert.Integer.trim to trim leading whitespace since it doesn't | |
379 // treat new-lines, etc. as whitespace which for our purposes is whitespace. | |
380 src = Util.trim (src); | |
381 | |
382 ate = cInt.trim (src, sign, radix); | |
383 if (ate == src.length) | |
384 throw new ParseException ("Invalid integer: no digits"); | |
385 ulong val = cInt.convert (src[ate..$], radix, &ate2); | |
386 ate += ate2; | |
387 | |
388 if (ate < src.length) | |
389 throw new ParseException ("Invalid integer at marked character: \"" ~ src[0..ate] ~ "'" ~ src[ate] ~ "'" ~ src[ate+1..$] ~ "\""); | |
390 | |
391 if (val > TInt.max) | |
392 throw new ParseException (INT_OUT_OF_RANGE); | |
393 if (sign) { | |
394 long sval = cast(long) -val; | |
395 if (sval > TInt.min) | |
396 return cast(TInt) sval; | |
397 else throw new ParseException (INT_OUT_OF_RANGE); | |
398 } | |
399 return cast(TInt) val; | |
400 } | |
401 | |
402 /* Basically a reimplementation of tango.text.convert.Float.toFloat which checks for | |
403 * whitespace before throwing an exception for overlong input. */ | |
404 private TFloat toTFloat(TFloat) (char[] src) { | |
405 // NOTE: As for toTInt(), this needs to strip leading as well as trailing whitespace. | |
406 src = Util.trim (src); | |
407 if (src == "") | |
408 throw new ParseException ("Invalid float: no digits"); | |
409 uint ate; | |
410 | |
411 TFloat x = cFloat.parse (src, &ate); | |
412 return x; | |
413 } | |
414 | |
415 /* Throws an exception on invalid escape sequences. Supported escape sequences are the following | |
416 * subset of those supported by D: \" \' \\ \a \b \f \n \r \t \v | |
417 */ | |
418 private char unEscapeChar (char c) | |
419 { | |
420 // This code was generated: | |
421 if (c <= 'b') { | |
422 if (c <= '\'') { | |
423 if (c == '\"') { | |
424 return '\"'; | |
425 } else if (c == '\'') { | |
426 return '\''; | |
427 } | |
428 } else { | |
429 if (c == '\\') { | |
430 return '\\'; | |
431 } else if (c == 'a') { | |
432 return '\a'; | |
433 } else if (c == 'b') { | |
434 return '\b'; | |
435 } | |
436 } | |
437 } else { | |
438 if (c <= 'n') { | |
439 if (c == 'f') { | |
440 return '\f'; | |
441 } else if (c == 'n') { | |
442 return '\n'; | |
443 } | |
444 } else { | |
445 if (c == 'r') { | |
446 return '\r'; | |
447 } else if (c == 't') { | |
448 return '\t'; | |
449 } else if (c == 'v') { | |
450 return '\v'; | |
451 } | |
452 } | |
453 } | |
454 | |
455 // if we haven't returned: | |
456 throw new ParseException ("Bad escape sequence: \\"~c); | |
457 } | |
458 | |
459 // Reads one hex char: [0-9A-Fa-f]. Otherwise throws an exception. Doesn't check src.length. | |
460 private ubyte readHexChar (char[] src, inout uint pos) { | |
461 ubyte x; | |
462 if (src[pos] >= '0' && src[pos] <= '9') x = src[pos] - '0'; | |
463 else if (src[pos] >= 'A' && src[pos] <= 'F') x = src[pos] - 'A' + 10; | |
464 else if (src[pos] >= 'a' && src[pos] <= 'f') x = src[pos] - 'a' + 10; | |
465 else throw new ParseException ("Invalid hex digit."); | |
466 ++pos; | |
467 return x; | |
468 } | |
469 | |
470 // Generic array reader | |
471 // Assumes input is of form "[xxxxx]" (i.e. first and last chars are '[', ']' and length >= 2). | |
472 private T[] toArray(T : T[]) (char[] src) { | |
473 T[] ret = new T[16]; // avoid unnecessary allocations | |
474 uint i = 0; | |
475 foreach (char[] element; split(src[1..$-1])) { | |
476 if (i == ret.length) ret.length = ret.length * 2; | |
477 ret[i] = deserialize!(T) (element); | |
478 ++i; | |
479 } | |
480 return ret[0..i]; | |
481 } | |
482 | |
483 /** Set a struct's elements from an array. | |
484 * | |
485 * For a more generic version, see http://www.dsource.org/projects/tutorials/wiki/StructTupleof | |
486 */ | |
487 // NOTE: Efficiency? Do recursive calls get inlined? | |
488 private void setStruct(S, size_t N, size_t i = 0) (ref S s, char[][N] src) { | |
489 static assert (is(S == struct), "Only to be used with structs."); | |
490 static assert (N == S.tupleof.length, "src.length != S.tupleof.length"); | |
491 static if (i < N) { | |
492 if (src[i]) | |
493 s.tupleof[i] = deserialize!(typeof(s.tupleof[i])) (src[i]); | |
494 setStruct!(S, N, i+1) (s, src); | |
495 } | |
496 } | |
497 //END Utility funcs | |
498 | |
499 debug (UnitTest) { | |
500 import tango.util.log.Log : Log, Logger; | |
501 | |
502 private Logger logger; | |
503 static this() { | |
504 logger = Log.getLogger ("text.deserialize"); | |
505 } | |
506 unittest { | |
507 // Utility | |
508 bool throws (void delegate() dg) { | |
509 bool r = false; | |
510 try { | |
511 dg(); | |
512 } catch (Exception e) { | |
513 r = true; | |
514 logger.info ("Exception caught: "~e.msg); | |
515 } | |
516 return r; | |
517 } | |
518 assert (!throws ({ int i = 5; })); | |
519 assert (throws ({ throw new Exception ("Test - this exception should be caught"); })); | |
520 | |
521 | |
522 // Associative arrays | |
523 char[][char] X = deserialize!(char[][char]) (`['a':"animal\n", 'b':['b','u','s','\n']]`); | |
524 char[][char] Y = ['a':cast(char[])"animal\n", 'b':['b','u','s','\n']]; | |
525 | |
526 //FIXME: when the compiler's fixed: http://d.puremagic.com/issues/show_bug.cgi?id=1671 | |
527 // just assert (X == Y) | |
528 assert (X.length == Y.length); | |
529 assert (X.keys == Y.keys); | |
530 assert (X.values == Y.values); | |
531 //X.rehash; Y.rehash; // doesn't make a difference | |
532 //assert (X == Y); // fails (compiler bug) | |
533 | |
534 assert (throws ({ deserialize!(int[int]) (`[1:1`); })); // bad brackets | |
535 assert (throws ({ deserialize!(int[char[]]) (`["ab\":1]`); })); // unterminated quote | |
536 assert (throws ({ deserialize!(int[char[]]) (`["abc,\a\b\c":1]`); })); // bad escape seq. | |
537 assert (throws ({ deserialize!(int[char[]]) (`["abc"]`); })); // no data | |
538 | |
539 | |
540 // Arrays | |
541 assert (deserialize!(double[]) (`[1.0,1.0e-10]`) == [1.0, 1.0e-10]);// generic array stuff | |
542 assert (deserialize!(double[]) (`[ ]`) == cast(double[]) []); // empty array | |
543 assert (deserialize!(int[][]) (`[[1],[2,3],[]]`) == [[1],[2,3],[]]);// sub-array | |
544 assert (throws ({ deserialize!(int[]) (`[1,2`); })); // bad brackets | |
545 assert (throws ({ deserialize!(int[][]) (`[[1]]]`); })); // bad brackets | |
546 | |
547 // char[] and char conversions, with commas, escape sequences and multichar UTF8 characters: | |
548 assert (deserialize!(char[][]) (`[ ".\"", [',','\''] ,"!\b€" ]`) == [ ".\"".dup, [',','\''] ,"!\b€" ]); | |
549 assert (throws ({ deserialize!(char[]) ("\"\\\""); })); | |
550 assert (throws ({ deserialize!(char[]) (`['a'`); })); // bad brackets | |
551 | |
552 // wchar[] and dchar[] conversions: | |
553 // The characters were pretty-much pulled at random from unicode tables. | |
554 // The last few cause some wierd (display only) effects in my editor. | |
555 assert (deserialize!(wchar[]) ("\"Test string: ¶α؟अกሀ搀\"") == "Test string: ¶α؟अกሀ搀"w); | |
556 assert (deserialize!(dchar[]) ("\"Test string: ¶α؟अกሀ搀\"") == "Test string: ¶α؟अกሀ搀"d); | |
557 | |
558 assert (deserialize!(ubyte[]) (`0x01F2aC`) == cast(ubyte[]) [0x01, 0xF2, 0xAC]); // ubyte[] special notation | |
559 assert (deserialize!(ubyte[]) (`[01 ,0xF2, 0xAC]`) == cast(ubyte[]) [0x01, 0xF2, 0xAC]); // ubyte[] std notation | |
560 assert (throws ({ deserialize!(ubyte[]) (`0x123`); })); // digits not in pairs | |
561 assert (throws ({ deserialize!(ubyte[]) (`[2,5`); })); // not [...] or 0x.. | |
562 assert (throws ({ deserialize!(ubyte[]) (`0x123j`); })); | |
563 | |
564 | |
565 // char types | |
566 assert (deserialize!(char) ("'\\\''") == '\''); | |
567 assert (deserialize!(wchar) ("'X'") == 'X'); | |
568 assert (deserialize!(dchar) ("'X'") == 'X'); | |
569 assert (throws ({ deserialize!(char) ("'\\'"); })); | |
570 assert (throws ({ deserialize!(char) ("'£'"); })); // non-ascii | |
571 assert (throws ({ deserialize!(char) ("''"); })); | |
572 assert (throws ({ deserialize!(char) ("'ab'"); })); | |
573 assert (throws ({ deserialize!(wchar) ("''"); })); | |
574 | |
575 | |
576 // bool | |
577 assert (deserialize!(bool[]) (`[true,false,01,00]`) == cast(bool[]) [1,0,1,0]); | |
578 assert (throws ({ deserialize!(bool) ("011"); })); | |
579 | |
580 | |
581 // ints | |
582 assert (deserialize!(byte) ("-5") == cast(byte) -5); | |
583 assert (deserialize!(int) ("-0x7FFFFFFF") == cast(int) -0x7FFF_FFFF); | |
584 // annoyingly, octal syntax differs from D (blame tango): | |
585 assert (deserialize!(uint[]) ("[0b0100,0o724,0xFa59c,0xFFFFFFFF,0]") == [0b0100u,0724,0xFa59c,0xFFFFFFFF,0]); | |
586 assert (throws ({ deserialize!(int) (""); })); | |
587 assert (throws ({ deserialize!(int) ("0x8FFFFFFF"); })); | |
588 assert (throws ({ deserialize!(uint) ("-1"); })); | |
589 assert (throws ({ deserialize!(uint) ("1a"); })); | |
590 | |
591 | |
592 // floats | |
593 assert (deserialize!(float) ("0.0") == 0.0f); | |
594 assert (deserialize!(double) ("-1e25") == -1e25); | |
595 assert (deserialize!(real) ("5.24e-269") == cast(real) 5.24e-269); | |
596 assert (throws ({ deserialize!(float) (""); })); | |
597 | |
598 | |
599 // structs | |
600 struct A { int x = 5; char y; } | |
601 struct B { A a; float b; } | |
602 A a; a.y = 'y'; | |
603 assert (deserialize!(A) ("{ 1 : 'y' }") == a); | |
604 B b; b.a = a; b.b = 1.0f; | |
605 assert (deserialize!(B) (" {1:1.0,0: { 1 : 'y' } } ") == b); | |
606 assert (throws ({ deserialize!(A) (" 1:'x'}"); })); // bad braces | |
607 assert (throws ({ deserialize!(A) ("{ 1 }"); })); // no :DATA | |
608 | |
609 | |
610 // unEscapeChar | |
611 assert (deserialize!(char[]) ("\"\\a\\b\\t\\n\\v\\f\\r\\\"\\\'\\\\\"") == "\a\b\t\n\v\f\r\"\'\\"); | |
612 | |
613 logger.info ("Unittest complete."); | |
614 } | |
615 } |