Changeset 79:61ea26abe4dd
- Timestamp:
- 08/05/08 06:51:51 (5 months ago)
- Files:
-
- codeDoc/jobs.txt (modified) (2 diffs)
- mde/font/font.d (modified) (2 diffs)
- mde/gui/WidgetData.d (modified) (1 diff)
- mde/gui/content/options.d (modified) (2 diffs)
- mde/input/Config.d (modified) (1 diff)
- mde/lookup/Options.d (modified) (6 diffs)
- mde/lookup/Translation.d (modified) (8 diffs)
- mde/mergetag/DataSet.d (modified) (1 diff)
- mde/mergetag/DefaultData.d (modified) (2 diffs)
- mde/mergetag/deserialize.d (moved) (moved from mde/mergetag/parse/parseTo.d) (15 diffs)
- mde/mergetag/serialize.d (moved) (moved from mde/mergetag/parse/parseFrom.d) (3 diffs)
- unittest/Translation.mtt (moved) (moved from data/L10n/i18nUnitTest.mtt) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
codeDoc/jobs.txt
r75 r79 4 4 5 5 In progress: 6 Redesigning how widgets are created and receive their data.7 6 8 7 … … 19 18 3 Scheduler for drawing only windows which need redrawing. 20 19 3 Update scheduler as outlined in FIXME. 21 3 Windows building/compatibility (currently partial) 20 3 Windows building/compatibility (currently partial) - tango/sys/win32/SpecialPath.d 22 21 2 Remove ability to scan, then load, mergetag sections. Not so necessary with section creator callback and allows "sliding window" type partial buffering. 23 22 2 Options need a "level": simple options, for advanced users, for debugging only, etc. mde/font/font.d
r76 r79 30 30 import derelict.opengl.gl; 31 31 32 import mde.mergetag. parse.parseTo : parseTo;32 import mde.mergetag.deserialize; 33 33 import tango.stdc.stringz; 34 34 import Util = tango.text.Util; … … 295 295 296 296 FT_Face face; 297 298 debug(mdeUnitTest) unittest { 299 // Don't do a unittest since font relies on loading the freetype library dynamically, 300 // normally done by Init. Also font is mostly visual and many problems will be obvious. 301 } 297 302 } 298 303 mde/gui/WidgetData.d
r78 r79 39 39 import mde.mergetag.Writer; 40 40 import mde.setup.paths; 41 import mde.mergetag.parse.parseTo; 42 import mde.mergetag.parse.parseFrom : parseFrom; 41 import mde.mergetag.serialize; 43 42 44 43 import tango.core.sync.Mutex; mde/gui/content/options.d
r73 r79 38 38 foreach (i,s; list) { 39 39 Translation.Entry transled = trans.getStruct (s); 40 textOpts[i] = new ContentOptionText(opts, s, transled. str, transled.desc);40 textOpts[i] = new ContentOptionText(opts, s, transled.name, transled.desc); 41 41 } 42 42 } … … 47 47 48 48 static OptionList trial () { 49 return new OptionList (miscOpts, " OptionsMisc");49 return new OptionList (miscOpts, "L10n/OptionsMisc"); 50 50 } 51 51 mde/input/Config.d
r74 r79 21 21 import MT = mde.mergetag.Reader; 22 22 import mde.setup.paths; 23 import mde.mergetag. parse.parseTo : parseTo;24 debug import mde.mergetag. parse.parseFrom : parseFrom;23 import mde.mergetag.deserialize; 24 debug import mde.mergetag.serialize; 25 25 26 26 import tango.util.log.Log : Log, Logger; mde/lookup/Options.d
r75 r79 29 29 import mde.mergetag.DataSet; 30 30 import mde.mergetag.exception; 31 import mde.mergetag.parse.parseTo : parseTo; 32 import mde.mergetag.parse.parseFrom : parseFrom; 31 import mde.mergetag.serialize; 33 32 34 33 import tango.core.Exception : ArrayBoundsException; … … 203 202 * must be changed in two separate places. */ 204 203 void set(T) (char[] symbol, T val) { 205 static if (!TIsIn!(T,TYPES)) 206 static assert (false, "Options.set does not currently support type "~T.stringof); 204 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof); 207 205 208 206 mixin (`alias opts`~TName!(T)~` optsVars;`); … … 223 221 * Using this method to read an option is not necessary, but allows for generic use. */ 224 222 T get(T) (char[] symbol) { 225 static if (!TIsIn!(T,TYPES)) 226 static assert (false, "Options.get does not currently support type "~T.stringof); 223 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof); 227 224 228 225 mixin (`alias opts`~TName!(T)~` optsVars;`); … … 238 235 /** List the names of all options of a specific type. */ 239 236 char[][] list(T) () { 240 static if (!TIsIn!(T,TYPES)) 241 static assert (false, "Options.list does not currently support type "~T.stringof); 237 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof); 242 238 243 239 mixin (`alias opts`~TName!(T)~` optsVars;`); … … 249 245 OptionChanges optionChanges; // all changes to options (for saving) 250 246 251 // The "pointer lists":247 // The "pointer lists", e.g. char[]*[ID] optscharA; 252 248 mixin (PLists!(TYPES)); 253 249 } … … 409 405 410 406 void set(T) (ID id, T x) { 411 static if (!TIsIn!(T,TYPES)) 412 static assert (false, "OptionChanges.set does not currently support type "~T.stringof); 407 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof); 413 408 414 409 mixin (`alias opts`~TName!(T)~` optsVars;`); mde/lookup/Translation.d
r75 r79 45 45 import mde.mergetag.Reader; 46 46 import mde.mergetag.exception; 47 import mde.mergetag. parse.parseTo;47 import mde.mergetag.deserialize; 48 48 49 49 import tango.util.log.Log : Log, Logger; … … 69 69 Entry* p = id in entries; 70 70 if (p) { 71 return p. str;71 return p.name; 72 72 } else { 73 73 return id; … … 79 79 if (p) { 80 80 description = p.desc; 81 return p. str;81 return p.name; 82 82 } else { 83 83 return id; … … 92 92 } else { 93 93 Entry ret; 94 ret. str= id;94 ret.name = id; 95 95 return ret; 96 96 } … … 118 118 IReader reader; 119 119 try { 120 reader = dataDir.makeMTReader ( "L10n/"~name, PRIORITY.HIGH_LOW);120 reader = dataDir.makeMTReader (name, PRIORITY.HIGH_LOW); 121 121 /* Note: we don't want to load every translation section depended on to its own class 122 122 * instance, since we want to merge them. So make every mergetag section use the same … … 168 168 void addTag (char[] tp, ID id, char[] dt) { 169 169 if (tp == "entry") { 170 char[][] fields = split (stripBrackets (dt)); 170 // If the tag already exists, don't replace it 171 if (cast(char[]) id in entries) return; 171 172 172 if (fields.length < 1) {173 // This tag is invalid, but this fact doesn't need to be reported elsewhere:173 Entry entry = deserialize!(Entry) (dt); 174 if (entry.name is null) { // This tag is invalid; ignore it 174 175 logger.error ("For name "~name~", L10n "~L10n~": tag with ID "~cast(char[])id~" has no data"); 175 176 return; 176 177 } 177 // If the tag already exists, don't replace it178 if (cast(char[]) id in entries) return;179 180 Entry entry;181 entry.str = parseTo!(char[]) (fields[0]);182 183 if (fields.length >= 2)184 entry.desc = parseTo!(char[]) (fields[1]);185 186 178 entries[cast(char[]) id] = entry; 187 179 } else if (tp == "char[][]") { … … 198 190 */ 199 191 struct Entry { 200 char[] str;// The translated string192 char[] name; // The translated string 201 193 char[] desc; // An optional description 202 194 } … … 237 229 miscOpts.L10n = "test-1"; 238 230 239 Translation transl = load (" i18nUnitTest");231 Translation transl = load ("unittest/Translation"); 240 232 241 233 // Simple get-string, check dependancy's entry doesn't override mde/mergetag/DataSet.d
r26 r79 64 64 ds.sec[cast(ID)"test"] = new DefaultData; 65 65 assert (ds.getSections!(DefaultData)().length == 1); 66 ds.sec[cast(ID)"test"].addTag (" int",cast(ID)"T"," -543");67 assert (ds.getSections!(DefaultData)()[cast(ID)"test"]. _int[cast(ID)"T"] == -543);66 ds.sec[cast(ID)"test"].addTag ("char[]",cast(ID)"T"," \"ut tag 1 \" "); 67 assert (ds.getSections!(DefaultData)()[cast(ID)"test"].Arg!(char[])[cast(ID)"T"] == "ut tag 1 "); 68 68 69 69 logger.info ("Unittest complete."); mde/mergetag/DefaultData.d
r75 r79 22 22 import mde.mergetag.exception; 23 23 24 import mde.mergetag.parse.parseTo : parseTo; 25 import mde.mergetag.parse.parseFrom : parseFrom; 24 import mde.mergetag.serialize; 26 25 27 26 … … 29 28 * Default DataSection class. 30 29 * 31 * Currently this is only used for headers, and thus the list of supported types has been 30 * Supported types are given by dataTypes. 31 * 32 * Currently DefaultData is only used for headers, and thus the list of supported types has been 32 33 * reduced to just those used in headers. Load order is HIGH_LOW, i.e. existing entries aren't 33 34 * overwritten. 34 *35 * It did supports most of the basic types supported by D (excluding cent/ucent and36 * imaginary/complex types) and array versions of each of these types, plus arrays of strings.37 *38 * Extending the class to support more types, even custom types, shouldn't be particularly39 * difficult provided mde.text.parseTo and mde.text.parseFrom are extended to support the new40 * types.41 35 *************************************************************************************************/ 42 36 /* The implementation now uses a fair bit of generic programming. Adjusting the types supported mde/mergetag/deserialize.d
r70 r79 1 1 /************************************************************************************************** 2 * Generic deserialization templated function. 3 * 2 4 * copyright: Copyright (c) 2007-2008 Diggory Hardy. 3 5 * 4 6 * author: Diggory Hardy, diggory.hardy@gmail.com 5 7 * 6 * license: BSD style: $(LICENSE) 7 * 8 * This contains templates for converting a char[] to various data-types. 9 * 10 * parseTo is roughly the inverse of $(B parseFrom) and should read any data output by $(B parseFrom). 11 * It is also available in tango.scrapple. 12 * 13 * This module basically implements the following templated function for most basic D types: 14 * bool, byte, short, int, long, ubyte, ushort, uint, ulong, float, double, real, char. 15 * It also supports arrays and associative arrays of any supported type (including of other arrays) 16 * and has special handling for strings (char[]) and binary (ubyte[]) data-types. 17 * ----------------------------- 18 * T parseTo(T) (char[] source); 19 * ----------------------------- 20 * 21 * $(I source) is the string to parse, and data of the templated type that is read from the string 22 * is returned. See the examples to get a better idea of its use. 23 * 24 * Syntax: 25 * The syntax for parsing $(I source) is mostly the same used by D without any prefixes/suffixes 26 * (except 0x, 0b & 0o base specifiers). Also a special ubyte[] syntax is supported; see examples. 27 * The following escape sequences are supported for strings and characters: \' \" \\ 28 * \a \b \f \n \r \t \v . Associative array literals use the same syntax as D, described here: 29 * $(LINK http://www.digitalmars.com/d/2.0/expression.html#AssocArrayLiteral). All whitespace is 30 * ignored (except of course within strings). 8 * Supports: 9 * Associative arrays, arrays (inc. strings), structs, char types, bool, int types, float types. 31 10 * 32 11 * There are also some public utility functions with their own documentation. … … 36 15 * suitable message. No other exceptions should be thrown. 37 16 * 38 * Remarks:39 * There is currently no support for reading wchar/dchar strings. There are, however, unicode40 * conversions for converting UTF-8 to UTF-16/32. Be careful if converting on a char-by-char basis;41 * such conversions cannot be used for non-ascii characters.42 *43 17 * Examples: 44 18 * ------------------------------------------------------------------------------------------------ 45 19 * // Basic examples: 46 * ulong a = parseTo!(ulong) ("20350");47 * float d = parseTo!(float) (" 1.2e-9 ");48 * int[] b = parseTo!(int[]) ("[0,1,2,3]");20 * ulong a = deserialize!(ulong) ("20350"); 21 * float d = deserialize!(float) (" 1.2e-9 "); 22 * int[] b = deserialize!(int[]) ("[0,1,2,3]"); 49 23 * 50 24 * // String and char[] syntax: 51 * char[] c = parseTo!(char[]) ("\"A string\"");52 * char[] e = parseTo!(char[]) ("['a','n','o','t','h','e','r', ' ' ,'s','t','r','i','n','g']");25 * char[] c = deserialize!(char[]) ("\"A string\""); 26 * char[] e = deserialize!(char[]) ("['a','n','o','t','h','e','r', ' ' ,'s','t','r','i','n','g']"); 53 27 * 54 28 * // These be used interchangably; here's a more complex example of an associative array: 55 * bool[char[]] f = parseTo!(bool[char[]]) ("[ \"one\":true, ['t','w','o']:false, \"three\":1, \"four\":000 ]");29 * bool[char[]] f = deserialize!(bool[char[]]) ("[ \"one\":true, ['t','w','o']:false, \"three\":1, \"four\":000 ]"); 56 30 * 57 31 * // There is also a special notation for ubyte[] types: 58 32 * // The digits following 0x must be in pairs and each specify one ubyte. 59 * assert ( parseTo!(ubyte[]) (`0x01F2AC`) == parseTo!(ubyte[]) (`[01 ,0xF2, 0xAC]`) );33 * assert ( deserialize!(ubyte[]) (`0x01F2AC`) == deserialize!(ubyte[]) (`[01 ,0xF2, 0xAC]`) ); 60 34 * 61 35 * // There's no limit to the complexity! 62 36 * char[char[][][][char]][bool] z = ...; // don't expect me to write this! 63 37 * ------------------------------------------------------------------------------------------------ 38 * 39 * TODO: Optimize memory allocation (if possible?). Test best sizes for initial allocations 40 * instead of merely guessing? 64 41 *************************************************************************************************/ 65 66 module mde.mergetag.parse.parseTo; 42 //NOTE: in case of multiple formats, make this a dummy module importing both serialize modules, 43 // or put all the code here. 44 module mde.mergetag.deserialize; 67 45 68 46 // tango imports … … 74 52 75 53 /** 76 * Base class for parseToexceptions.54 * Base class for deserialize exceptions. 77 55 */ 78 56 class ParseException : TextException … … 84 62 } 85 63 86 87 //BEGIN parseTo templates 64 alias deserialize parseTo; // support the old name 65 66 //BEGIN deserialize templates 88 67 89 68 // Associative arrays 90 69 91 const char[] AA_ERR = "Invalid associative array: "; 92 T[S] parseTo(T : T[S], S) (char[] src) { 70 T[S] deserialize(T : T[S], S) (char[] src) { 93 71 src = Util.trim(src); 94 72 if (src.length < 2 || src[0] != '[' || src[$-1] != ']') 95 throw new ParseException ( AA_ERR ~ "not [ ... ]");// bad braces.73 throw new ParseException ("Invalid associative array: not [ ... ]"); // bad braces. 96 74 97 75 T[S] ret; 98 76 foreach (char[] pair; split (src[1..$-1])) { 99 77 uint i = 0; 100 while (i < pair.length) { // advance to the ':'78 while (i < pair.length) { // advance to the ':' 101 79 char c = pair[i]; 102 80 if (c == ':') break; 103 if (c == '\'' || c == '"') { // string or character81 if (c == '\'' || c == '"') { // string or character 104 82 ++i; 105 83 while (i < pair.length && pair[i] != c) { 106 if (pair[i] == '\\') { 107 if (i+2 >= pair.length) throw new ParseException (AA_ERR ~ "unfinished escape sequence within string/char"); 108 ++i; // escape seq. 109 } 84 if (pair[i] == '\\') 85 ++i; // escape seq. 110 86 ++i; 111 87 } 112 if (i == pair.length) {113 throw new ParseException (AA_ERR ~ "encountered [ ... KEY] (missing :DATA)");114 }88 // Could have an unterminated ' or " causing i >= pair.length, but: 89 // 1. Impossible: split would have thrown 90 // 2. In any case this would be caught below. 115 91 } 116 92 ++i; 117 93 } 118 if (i == pair.length) { 119 throw new ParseException (AA_ERR ~ "encountered [ ... KEY:] (missing DATA)"); 120 } 121 ret[parseTo!(S) (pair[0..i])] = parseTo!(T) (pair[i+1..$]); 94 if (i >= pair.length) 95 throw new ParseException ("Invalid associative array: encountered [ ... KEY] (missing :DATA)"); 96 ret[deserialize!(S) (pair[0..i])] = deserialize!(T) (pair[i+1..$]); 122 97 } 123 98 return ret; 124 99 } 125 debug (UnitTest) unittest {126 char[][char] X = parseTo!(char[][char]) (`['a':"animal", 'b':['b','u','s']]`);127 char[][char] Y = ['a':cast(char[])"animal", 'b':['b','u','s']];128 129 //FIXME: when the compiler's fixed: http://d.puremagic.com/issues/show_bug.cgi?id=1671130 // just assert (X == Y)131 assert (X.length == Y.length);132 assert (X.keys == Y.keys);133 assert (X.values == Y.values);134 //X.rehash; Y.rehash; // doesn't make a difference135 //assert (X == Y); // fails (compiler bug)136 }137 100 138 101 139 102 // Arrays 140 103 141 T[] parseTo(T : T[]) (char[] src) {104 T[] deserialize(T : T[]) (char[] src) { 142 105 src = Util.trim(src); 143 if (src.length >= 2 && src[0] == '[' && src[$-1] == ']') return toArray!(T[]) (src); 144 throw new ParseException ("Invalid array: not [x, ..., z]"); 106 if (src.length >= 2 && src[0] == '[' && src[$-1] == ']') 107 return toArray!(T[]) (src); 108 throw new ParseException ("Invalid array: not [ ... ]"); 145 109 } 146 110 147 111 // String (array special case) 148 T parseTo(T : char[]) (char[] src) {112 T deserialize(T : char[]) (char[] src) { 149 113 src = Util.trim(src); 150 114 if (src.length >= 2 && src[0] == '"' && src[$-1] == '"') { 151 115 src = src[1..$-1]; 152 116 T ret; 153 ret.length = src.length; // maximum length; retract to actual length later117 ret.length = src.length; // maximum length; retract to actual length later 154 118 uint i = 0; 155 119 for (uint t = 0; t < src.length;) { 156 120 // process a block of non-escaped characters 157 121 uint s = t; 158 while (t < src.length && src[t] != '\\') ++t; // non-escaped characters122 while (t < src.length && src[t] != '\\') ++t; // non-escaped characters 159 123 uint j = i + t - s; 160 ret[i..j] = src[s..t]; // copy a block124 ret[i..j] = src[s..t]; // copy a block 161 125 i = j; 162 126 … … 164 128 while (t < src.length && src[t] == '\\') { 165 129 t++; 166 if (t == src.length) throw new ParseException ("Invalid string: ends \\\" !"); // next char is " 167 ret[i++] = replaceEscapedChar (src[t++]); // throws if it's invalid 130 if (t == src.length) 131 throw new ParseException ("Invalid string: ends \\\" !"); // next char is " 132 ret[i++] = unEscapeChar (src[t++]); // throws if it's invalid 168 133 } 169 134 } 170 135 return ret[0..i]; 171 136 } 172 else if (src.length >= 2 && src[0] == '[' && src[$-1] == ']') return toArray!(T) (src); 137 else if (src.length >= 2 && src[0] == '[' && src[$-1] == ']') 138 return toArray!(T) (src); 173 139 throw new ParseException ("Invalid string: not quoted (\"*\") or char array (['a',...,'c'])"); 174 140 } 175 141 // Unicode conversions for strings: 176 T parseTo(T : wchar[]) (char[] src) {142 T deserialize(T : wchar[]) (char[] src) { 177 143 // May throw a UnicodeException; don't bother catching and rethrowing: 178 return Utf.toString16 ( parseTo!(char[]) (src));179 } 180 T parseTo(T : dchar[]) (char[] src) {144 return Utf.toString16 (deserialize!(char[]) (src)); 145 } 146 T deserialize(T : dchar[]) (char[] src) { 181 147 // May throw a UnicodeException; don't bother catching and rethrowing: 182 return Utf.toString32 ( parseTo!(char[]) (src));148 return Utf.toString32 (deserialize!(char[]) (src)); 183 149 } 184 150 185 151 // Binary (array special case) 186 T parseTo(T : ubyte[]) (char[] src) {152 T deserialize(T : ubyte[]) (char[] src) { 187 153 src = Util.trim(src); 188 154 // Standard case: … … 193 159 194 160 // Must be in pairs: 195 if (src.length % 2 == 1) throw new ParseException ("Invalid binary: odd number of chars"); 161 if (src.length % 2 == 1) 162 throw new ParseException ("Invalid binary: odd number of chars"); 196 163 197 164 T ret; 198 ret.length = src.length / 2; // exact165 ret.length = src.length / 2; // exact 199 166 200 167 for (uint i, pos; pos + 1 < src.length; ++i) { … … 208 175 } 209 176 210 debug (UnitTest) unittest {211 assert (parseTo!(double[]) (`[1.0,1.0e-10]`) == [1.0, 1.0e-10]); // generic array stuff212 assert (parseTo!(double[]) (`[ ]`) == cast(double[]) []); // empty array213 214 // char[] and char conversions, with commas, escape sequences and multichar UTF8 characters:215 assert (parseTo!(char[][]) (`[ ".\"", [',','\''] ,"!\bâ¬" ]`) == [ ".\"".dup, [',','\''] ,"!\bâ¬" ]);216 217 // wchar[] and dchar[] conversions:218 // The characters were pretty-much pulled at random from unicode tables.219 // The last few cause some wierd (display only) effects in my editor.220 assert (parseTo!(wchar[]) ("\"Test string: ¶αØà€221 àžáæ\"") == "Test string: ¶αØà€222 àžáæ"w);223 assert (parseTo!(dchar[]) ("\"Test string: ¶αØà€224 àžáæ\"") == "Test string: ¶αØà€225 àžáæ"d);226 227 assert (parseTo!(ubyte[]) (`0x01F2AC`) == cast(ubyte[]) [0x01, 0xF2, 0xAC]); // ubyte[] special notation228 assert (parseTo!(ubyte[]) (`[01 ,0xF2, 0xAC]`) == cast(ubyte[]) [0x01, 0xF2, 0xAC]); // ubyte[] std notation229 }230 231 177 232 178 // Basic types 233 179 234 180 // Char 235 T parseTo(T : char) (char[] src) { 181 // Assumes value is <= 127 (for valid UTF-8), since input would be invalid UTF-8 if not anyway. 182 // (And we're not really interested in checking for valid unicode; char[] conversions don't either.) 183 T deserialize(T : char) (char[] src) { 236 184 src = Util.trim(src); 237 185 if (src.length < 3 || src[0] != '\'' || src[$-1] != '\'') 238 throw new ParseException ("Invalid char: not quoted (e.g. 'c')"); 239 if (src[1] != '\\' && src.length == 3) return src[1]; // Either non escaped 240 if (src.length == 4) return replaceEscapedChar (src[2]); // Or escaped 241 242 // Report various errors; warnings for likely and difficult to tell cases: 243 // Warn in case it's a multibyte UTF-8 character: 244 if (src[1] & 0xC0u) throw new UnicodeException ("Invalid char: too long (non-ASCII UTF-8 characters cannot be read as a single character)", 1); 245 throw new ParseException ("Invalid char: too long"); 246 } 247 /* Basic unicode convertions for wide-chars. 248 * NOTE: c > 127 signals the start of a multibyte UTF-8 sequence which must be converted for 249 * UTF-16/32. But since we don't know what the next bytes are we can't do the conversion. */ 250 const char[] WIDE_CHAR_ERROR = "Error: unicode non-ascii character cannot be converted from a single UTF-8 char"; 251 T parseTo(T : wchar) (char[] src) { 252 char c = parseTo!(char) (src); 253 if (c <= 127u) return cast(wchar) c; // this char can be converted 254 else throw new UnicodeException (WIDE_CHAR_ERROR, 1); 255 } 256 T parseTo(T : dchar) (char[] src) { 257 char c = parseTo!(char) (src); 258 if (c <= 127u) return cast(dchar) c; // this char can be converted 259 else throw new UnicodeException (WIDE_CHAR_ERROR, 1); 186 throw new ParseException ("Invalid char: not 'x' or '\\x'"); 187 if (src[1] != '\\') { 188 if (src.length == 3) 189 return src[1]; // Either non escaped 190 throw new ParseException ("Invalid char: too long (or non-ASCII)"); 191 } else if (src.length == 4) 192 return unEscapeChar (src[2]); // Or escaped 193 194 throw new ParseException ("Invalid char: '\\'"); 195 } 196 // Basic unicode convertions for wide-chars. 197 // Assumes value is <= 127 as does deserialize!(char). 198 T deserialize(T : wchar) (char[] src) { 199 return cast(T) deserialize!(char) (src); 200 } 201 T deserialize(T : dchar) (char[] src) { 202 return cast(T) deserialize!(char) (src); 203 } 204 205 // Bool 206 T deserialize(T : bool) (char[] src) { 207 src = Util.trim(src); 208 if (src == "true") 209 return true; 210 if (src == "false") 211 return false; 212 uint pos; 213 while (src.length > pos && src[pos] == '0') ++pos; // skip leading zeros 214 if (src.length == pos && pos > 0) 215 return false; 216 if (src.length == pos + 1 && src[pos] == '1') 217 return true; 218 throw new ParseException ("Invalid bool: not true or false and doesn't evaluate to 0 or 1"); 219 } 220 221 // Ints 222 T deserialize(T : byte) (char[] src) { 223 return toTInt!(T) (src); 224 } 225 T deserialize(T : short) (char[] src) { 226 return toTInt!(T) (src); 227 } 228 T deserialize(T : int) (char[] src) { 229 return toTInt!(T) (src); 230 } 231 T deserialize(T : long) (char[] src) { 232 return toTInt!(T) (src); 233 } 234 T deserialize(T : ubyte) (char[] src) { 235 return toTInt!(T) (src); 236 } 237 T deserialize(T : ushort) (char[] src) { 238 return toTInt!(T) (src); 239 } 240 T deserialize(T : uint) (char[] src) { 241 return toTInt!(T) (src); 242 } 243 T deserialize(T : ulong) (char[] src) { 244 return toTInt!(T) (src); 260 245 } 261 246 debug (UnitTest) unittest { 262 assert (parseTo!(char) ("\'\\\'\'") == '\''); 263 assert (parseTo!(wchar) ("'X'") == 'X'); 264 assert (parseTo!(dchar) ("'X'") == 'X'); 265 } 266 267 // Bool 268 T parseTo(T : bool) (char[] src) { 247 assert (deserialize!(byte) ("-5") == cast(byte) -5); 248 // annoyingly, octal syntax differs from D (blame tango): 249 assert (deserialize!(uint[]) ("[0b0100,0o724,0xFa59c,0xFFFFFFFF,0]") == [0b0100u,0724,0xFa59c,0xFFFFFFFF,0]); 250 } 251 252 // Floats 253 T deserialize(T : float) (char[] src) { 254 return toTFloat!(T) (src); 255 } 256 T deserialize(T : double) (char[] src) { 257 return toTFloat!(T) (src); 258 } 259 T deserialize(T : real) (char[] src) { 260 return toTFloat!(T) (src); 261 } 262 263 264 // Structs 265 T deserialize(T) (char[] src) { 266 static assert (is(T == struct), "Unsupported type: "~typeof(T)); 267 269 268 src = Util.trim(src); 270 if (src == "true") return true; 271 if (src == "false") return false; 272 uint pos; 273 while (src.length > pos && src[pos] == '0') ++pos; // skip leading zeros 274 if (src.length == pos && pos > 0) return false; 275 if (src.length == pos + 1 && src[pos] == '1') return true; 276 throw new ParseException ("Invalid bool: not true or false and doesn't evaluate to 0 or 1"); 277 } 278 debug (UnitTest) unittest { 279 assert (parseTo!(bool[]) (`[true,false,01,00]`) == cast(bool[]) [1,0,1,0]); 280 } 281 282 // Ints 283 T parseTo(T : byte) (char[] src) { 284 return toTInt!(T) (src); 285 } 286 T parseTo(T : short) (char[] src) { 287 return toTInt!(T) (src); 288 } 289 T parseTo(T : int) (char[] src) { 290 return toTInt!(T) (src); 291 } 292 T parseTo(T : long) (char[] src) { 293 return toTInt!(T) (src); 294 } 295 T parseTo(T : ubyte) (char[] src) { 296 return toTInt!(T) (src); 297 } 298 T parseTo(T : ushort) (char[] src) { 299 return toTInt!(T) (src); 300 } 301 T parseTo(T : uint) (char[] src) { 302 return toTInt!(T) (src); 303 } 304 T parseTo(T : ulong) (char[] src) { 305 return toTInt!(T) (src); 306 } 307 debug (UnitTest) unittest { 308 assert (parseTo!(byte) ("-5") == cast(byte) -5); 309 // annoyingly, octal syntax differs from D (blame tango): 310 assert (parseTo!(uint[]) ("[0b0100,0o724,0xFa59c,0xFFFFFFFF,0]") == [0b0100u,0724,0xFa59c,0xFFFFFFFF,0]); 311 } 312 313 // Floats 314 T parseTo(T : float) (char[] src) { 315 return toTFloat!(T) (src); 316 } 317 T parseTo(T : double) (char[] src) { 318 return toTFloat!(T) (src); 319 } 320 T parseTo(T : real) (char[] src) { 321 return toTFloat!(T) (src); 322 } 323 debug (UnitTest) unittest { 324 assert (parseTo!(float) ("0.0") == 0.0f); 325 assert (parseTo!(double) ("-1e25") == -1e25); 326 assert (parseTo!(real) ("5.24e-269") == cast(real) 5.24e-269); 327 } 328 //END parseTo templates 269 if (src.length < 2 || src[0] != '{' || src[$-1] != '}') 270 throw new ParseException ("Invalid struct: not { ... }"); 271 272 // cannot access elements of T.tupleof with non-const key, so use a type which can be 273 // accessed with a non-const key to store slices: 274 char[][T.tupleof.length] temp; 275 foreach (char[] pair; split (src[1..$-1])) { 276 uint i = 0; 277 while (i < pair.length) { // advance to the ':' 278 char c = pair[i]; 279 if (c == ':') 280 break; 281 // key must be an int so no need for string checks 282 ++i; 283 } 284 if (i >= pair.length) 285 throw new ParseException ("Invalid struct: encountered { ... KEY} (missing :DATA)"); 286 287 size_t k = deserialize!(size_t) (pair[0..i]); 288 // Note: could check no entry was already stored in temp. 289 temp[k] = pair[i+1..$]; 290 } 291 T ret; 292 setStruct (ret, temp); 293 return ret; 294 } 295 //END deserialize templates 329 296 330 297 //BEGIN Utility funcs 331 /** Trims whitespace at ends of string and checks for and removes array brackets: []332 *333 * Throws:334 * ParseException if brackets aren't end non-whitespace characters.335 *336 * Returns:337 * String without brackets (and whitespace outside those brackets). Useful for passing to split.338 */339 char[] stripBrackets (char[] src) {340 src = Util.trim(src);341 if (src.length >= 2 && src[0] == '[' && src[$-1] == ']') return src[1..$-1];342 throw new ParseException ("Invalid bracketed string: not [...]");343 }344 345 298 /** Splits a string into substrings separated by '$(B ,)' with support for characters and strings 346 299 * containing escape sequences and for embedded arrays ($(B [...])). 347 300 * 348 301 * Params: 349 * src A string to separate on commas. Where used for parsing arrays, the brackets enclosing 350 * the array should be removed before calling this function (stripBrackets can do this). 302 * src A string to separate on commas. It shouldn't have enclosing brackets. 351 303 * 352 304 * Returns: … … 359 311 * fact no brackets are stripped from src. 360 312 */ 313 //FIXME foreach struct is more efficient 361 314 char[][] split (char[] src) { 362 315 src = Util.trim (src); 363 if (src == "") return []; // empty array: no elements when no data 364 365 uint depth = 0; // surface depth (embedded arrays) 316 if (src == "") 317 return []; // empty array: no elements when no data 318 319 uint depth = 0; // surface depth (embedded arrays) 366 320 char[][] ret; 367 ret.length = src.length / 3; // unlikely to need a longer array368 uint k = 0; // current split piece369 uint i = 0, j = 0; // current read location, start of current piece321 ret.length = src.length / 3; // unlikely to need a longer array 322 uint k = 0; // current split piece 323 uint i = 0, j = 0; // current read location, start of current piece 370 324 371 325 while (i < src.length) { 372 326 char c = src[i]; 373 if (c == '\'' || c == '"') { // string or character327 if (c == '\'' || c == '"') { // string or character 374 328 ++i; 375 329 while (i < src.length && src[i] != c) { 376 if (src[i] == '\\') ++i; // escape seq. 330 if (src[i] == '\\') 331 ++i; // escape seq. 377 332 ++i; 378 } // Doesn't throw if no terminal quote at end of src, but this should be caught later.333 } // Doesn't throw if no terminal quote at end of src, but this should be caught later. 379 334 } 380 335 else if (c == '[') ++depth; 381 336 else if (c == ']') { 382 if (depth) --depth; 337 if (depth) 338 --depth; 383 339 else throw new ParseException ("Invalid array literal: closes before end of data item."); 384 340 } 385 else if (c == ',' && depth == 0) { // only if not an embedded array 386 if (ret.length <= k) ret.length = ret.length * 2; 387 ret[k++] = src[j..i]; // add this piece and increment k 341 else if (c == ',' && depth == 0) { // only if not an embedded array 342 if (ret.length <= k) 343 ret.length = ret.length * 2; 344 ret[k++] = src[j..i]; // add this piece and increment k 388 345 j = i + 1; 389 346 } 390 347 ++i; 391 348 } 392 if (ret.length <= k) ret.length = k + 1; 393 ret[k] = src[j..i]; // add final piece (i >= j) 349 if (i > src.length) 350 throw new ParseException ("Unterminated quote (\' or \")"); 351 352 if (ret.length <= k) 353 ret.length = k + 1; 354 ret[k] = src[j..i]; // add final piece (i >= j) 394 355 return ret[0..k+1]; 395 356 } … … 410 371 411 372 ate = cInt.trim (src, sign, radix); 412 if (ate == src.length) throw new ParseException ("Invalid integer: no digits"); 373 if (ate == src.length) 374 throw new ParseException ("Invalid integer: no digits"); 413 375 ulong val = cInt.convert (src[ate..$], radix, &ate2); 414 376 ate += ate2; … … 417 379 throw new ParseException ("Invalid integer at marked character: \"" ~ src[0..ate] ~ "'" ~ src[ate] ~ "'" ~ src[ate+1..$] ~ "\""); 418 380 419 if (val > TInt.max) throw new ParseException (INT_OUT_OF_RANGE); 381 if (val > TInt.max) 382 throw new ParseException (INT_OUT_OF_RANGE); 420 383 if (sign) { 421 384 long sval = cast(long) -val; 422 if (sval > TInt.min) return cast(TInt) sval; 385 if (sval > TInt.min) 386 return cast(TInt) sval; 423 387 else throw new ParseException (INT_OUT_OF_RANGE); 424 388 } … … 431 395 // NOTE: As for toTInt(), this needs to strip leading as well as trailing whitespace. 432 396 src = Util.trim (src); 433 if (src == "") throw new ParseException ("Invalid float: no digits"); 397 if (src == "") 398 throw new ParseException ("Invalid float: no digits"); 434 399 uint ate; 435 400 … … 441 406 * subset of those supported by D: \" \' \\ \a \b \f \n \r \t \v 442 407 */ 443 private char replaceEscapedChar (char c)408 private char unEscapeChar (char c) 444 409 { 445 410 // This code was generated: … … 479 444 480 445 // if we haven't returned: 481 throw new ParseException (" Invalid escape sequence: \\"~c);446 throw new ParseException ("Bad escape sequence: \\"~c); 482 447 } 483 448 … … 496 461 // Assumes input is of form "[xxxxx]" (i.e. first and last chars are '[', ']' and length >= 2). 497 462 private T[] toArray(T : T[]) (char[] src) { 498 T[] ret = new T[16]; // avoid unnecessary allocations463 T[] ret = new T[16]; // avoid unnecessary allocations 499 464 uint i = 0; 500 465 foreach (char[] element; split(src[1..$-1])) { 501 466 if (i == ret.length) ret.length = ret.length * 2; 502 ret[i] = parseTo!(T) (element);467 ret[i] = deserialize!(T) (element); 503 468 ++i; 504 469 } … … 506 471 } 507 472 473 /** Set a struct's elements from an array. 474 * 475 * For a more generic version, see http://www.dsource.org/projects/tutorials/wiki/StructTupleof 476 */ 477 // NOTE: Efficiency? Do recursive calls get inlined? 478 private void setStruct(S, size_t N, size_t i = 0) (ref S s, char[][N] src) { 479 static assert (is(S == struct), "Only to be used with structs."); 480 static assert (N == S.tupleof.length, "src.length != S.tupleof.length"); 481 static if (i < N) { 482 if (src[i]) 483 s.tupleof[i] = deserialize!(typeof(s.tupleof[i])) (src[i]); 484 setStruct!(S, N, i+1) (s, src); 485 } 486 } 487 //END Utility funcs 488 508 489 debug (UnitTest) { 509 import tango.io.Console; 510 511 unittest { 512 Cout ("Running unittest: parseTo ...").flush; 513 514 assert (parseTo!(char[]) ("\"\\a\\b\\t\\n\\v\\f\\r\\\"\\\'\\\\\"") == "\a\b\t\n\v\f\r\"\'\\"); 515 516 Cout (" complete").newline; 517 } 518 } 519 //END Utility funcs 490 import tango.util.log.Log : Log, Logger; 491 492 private Logger logger; 493 static this() { 494 logger = Log.getLogger ("text.deserialize"); 495 } 496 unittest { 497 // Utility 498 bool throws (void delegate() dg) { 499 bool r = false; 500 try { 501 dg(); 502 } catch (Exception e) { 503 r = true; 504 logger.info ("Exception caught: "~e.msg); 505 } 506 return r; 507 } 508 assert (!throws ({ int i = 5; })); 509 assert (throws ({ throw new Exception ("Test - this exception should be caught"); })); 510 511 512 // Associative arrays 513 char[][char] X = deserialize!(char[][char]) (`['a':"animal\n", 'b':['b','u','s','\n']]`); 514 char[][char] Y = ['a':cast(char[])"animal\n", 'b':['b','u','s','\n']]; 515 516 //FIXME: when the compiler's fixed: http://d.puremagic.com/issues/show_bug.cgi?id=1671 517 // just assert (X == Y) 518 assert (X.length == Y.length); 519 assert (X.keys == Y.keys); 520 assert (X.values == Y.values); 521 //X.rehash; Y.rehash; // doesn't make a difference 522 //assert (X == Y); // fails (compiler bug) 523 524 assert (throws ({ deserialize!(int[int]) (`[1:1`); })); // bad brackets 525 assert (throws ({ deserialize!(int[char[]]) (`["ab\":1]`); })); // unterminated quote 526 assert (throws ({ deserialize!(int[char[]]) (`["abc,\a\b\c":1]`); })); // bad escape seq.&nbs
