comparison mde/lookup/Options.d @ 64:cc3763817b8a

Overhauled Options so that it now uses templates and mixins for type-specific internals, and supported types can be adjusted via just one list.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sun, 29 Jun 2008 11:55:55 +0100
parents 66d555da083e
children 108d123238c0
comparison
equal deleted inserted replaced
63:66d555da083e 64:cc3763817b8a
42 * instances and provides loading and saving methods. 42 * instances and provides loading and saving methods.
43 * 43 *
44 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references 44 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references
45 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from 45 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from
46 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will 46 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will
47 * not be saved; use, for example, Options.setBool(...). Use an example like OptionsMisc as a 47 * not be saved; instead use set(), for example, miscOpts.set!(char[])("L10n","en-GB"). Use an
48 * template for creating a new Options sub-class. 48 * example like OptionsMisc as a template for creating a new Options sub-class.
49 * 49 *
50 * Details: Options sub-classes hold associative arrays of pointers to all option variables, with a 50 * Details: Options sub-classes hold associative arrays of pointers to all option variables, with a
51 * char[] id. This list is used for saving, loading and to provide generic GUI options screens. The 51 * char[] id. This list is used for saving, loading and to provide generic GUI options screens. The
52 * built-in support in Options is only for bool, int and char[] types (a float type may get added). 52 * built-in support in Options is only for bool, int and char[] types (a float type may get added).
53 * Further to this, a generic class is used to store all options which have been changed, and if any 53 * Further to this, a generic class is used to store all options which have been changed, and if any
54 * have been changed, is merged with options from the user conf dir and saved on exit. 54 * have been changed, is merged with options from the user conf dir and saved on exit.
55 */ 55 */
56 class Options : IDataSection 56 class Options : IDataSection
57 { 57 {
58 // No actual options are stored by this class. However, much of the infrastructure is 58 // All supported types, for generic handling via templates. It should be possible to change
59 // present since it need not be redefined in sub-classes. 59 // the supported types simply by changing this list now (untested).
60 60 template store(A...) { alias A store; }
61 // The "pointer lists": 61 alias store!(bool, int, double, char[]) TYPES;
62 protected bool* [ID] optsBool; 62 //BEGIN Templates: internal
63 protected int* [ID] optsInt; 63 private {
64 protected double*[ID] optsDouble; 64 // Get name of a type. Basically just stringof, but special handling for arrays.
65 protected char[]*[ID] optsCharA; 65 // Use TName!(T) for a valid symbol name, and T.stringof for a type.
66 template TName(T : T[]) {
67 const char[] TName = TName!(T) ~ "A";
68 }
69 template TName(T) {
70 const char[] TName = T.stringof;
71 }
72
73 // Pointer lists
74 template PLists(T, A...) {
75 static if (A.length) {
76 const char[] PLists = T.stringof~"*[ID] opts"~TName!(T)~";\n" ~ PLists!(A);
77 } else
78 const char[] PLists = T.stringof~"*[ID] opts"~TName!(T)~";\n";
79 }
80
81 // True if type is one of A
82 template TIsIn(T, A...) {
83 static if (A.length) {
84 static if (is(T == A[0]))
85 const bool TIsIn = true;
86 else
87 const bool TIsIn = TIsIn!(T,A[1..$]);
88 } else
89 const bool TIsIn = false; // no more possibilities
90 }
91
92 // For addTag
93 template addTagMixin(T, A...) {
94 const char[] ifBlock = `if (tp == "`~T.stringof~`") {
95 `~T.stringof~`** p = id in opts`~TName!(T)~`;
96 if (p !is null) **p = parseTo!(`~T.stringof~`) (dt);
97 }`;
98 static if (A.length)
99 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin;
100 else
101 const char[] addTagMixin = ifBlock;
102 }
103
104 // For writeAll
105 template writeAllMixin(A...) {
106 static if (A.length) {
107 const char[] writeAllMixin =
108 `foreach (ID id, `~A[0].stringof~`* val; opts`~TName!(A[0])~`) dlg ("`~A[0].stringof~`", id, parseFrom!(`~A[0].stringof~`) (*val));` ~ writeAllMixin!(A[1..$]);
109 } else
110 const char[] writeAllMixin = ``;
111 }
112 }
113 //END Templates: internal
114
115
116 //BEGIN Static
117 static {
118 /** Add an options sub-class to the list for loading and saving.
119 *
120 * Call from static this() (before Init calls load()). */
121 void addOptionsClass (Options c, char[] i)
122 in { // Trap a couple of potential coding errors:
123 assert (c !is null); // Instance must be created before calling addOptionsClass
124 assert (((cast(ID) i) in subClasses) is null); // Don't allow a silent replacement
125 } body {
126 subClasses[cast(ID) i] = c;
127 }
128
129 // Track all sections for saving/loading/other generic handling.
130 Options[ID] subClasses;
131 bool changed = false; // any changes at all, i.e. do we need to save?
132
133 /* Load/save options from file.
134 *
135 * If the file doesn't exist, no reading is attempted (options are left at default values).
136 */
137 private const fileName = "options";
138 private const MT_LOAD_EXC = "Loading options aborted:";
139 void load () {
140 // Check it exists (if not it should still be created on exit).
141 // Don't bother checking it's not a folder, because it could still be a block or something.
142 if (!confDir.exists (fileName)) return;
143
144 try {
145 IReader reader;
146 reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
147 reader.dataSecCreator = delegate IDataSection(ID id) {
148 /* Recognise each defined section, and return null for unrecognised sections. */
149 Options* p = id in subClasses;
150 if (p !is null) return *p;
151 else return null;
152 };
153 reader.read;
154 } catch (MTException e) {
155 logger.fatal (MT_LOAD_EXC);
156 logger.fatal (e.msg);
157 throw new optionsLoadException ("Mergetag exception (see above message)");
158 }
159 }
160 void save () {
161 if (!changed) return; // no changes to save
162
163 DataSet ds = new DataSet();
164 foreach (id, subOpts; subClasses) ds.sec[id] = subOpts.optionChanges;
165
166 // Read locally-stored options
167 try {
168 IReader reader;
169 reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, ds);
170 reader.dataSecCreator = delegate IDataSection(ID id) {
171 return null; // All recognised sections are already in the dataset.
172 };
173 reader.read;
174 } catch (MTFileIOException) {
175 // File either didn't exist or couldn't be opened.
176 // Presuming the former, this is not a problem.
177 } catch (MTException e) {
178 // Log a message and continue, overwriting the file:
179 logger.error (MT_LOAD_EXC);
180 logger.error (e.msg);
181 }
182
183 try {
184 IWriter writer;
185 writer = confDir.makeMTWriter (fileName, ds);
186 writer.write();
187 } catch (MTException e) {
188 logger.error ("Saving options aborted! Reason:");
189 logger.error (e.msg);
190 }
191 }
192
193 private Logger logger;
194 static this() {
195 logger = Log.getLogger ("mde.options");
196 }
197 }
198 //END Static
199
200
201 //BEGIN Non-static
202 /** Set option symbol of an Options sub-class to val.
203 *
204 * Due to the way options are handled generically, string IDs must be used to access the options
205 * via hash-maps, which is a little slower than direct access but necessary since the option
206 * must be changed in two separate places. */
207 void set(T) (char[] symbol, T val) {
208 static if (!TIsIn!(T,TYPES))
209 static assert (false, "Options.set does not currently support type "~T.stringof);
210
211 mixin (`alias opts`~T.stringof~` optsVars;`);
212
213 changed = true; // something got set (don't bother checking this isn't what it already was)
214
215 try {
216 *(optsVars[cast(ID) symbol]) = val;
217 optionChanges.set!(T) (cast(ID) symbol, val);
218 } catch (ArrayBoundsException) {
219 // log and ignore:
220 logger.error ("Options.set: unkw!");
221 }
222 }
223
224 protected {
225 OptionChanges optionChanges; // all changes to options (for saving)
226
227 // The "pointer lists":
228 mixin (PLists!(TYPES));
229 }
66 230
67 //BEGIN Mergetag loading/saving code 231 //BEGIN Mergetag loading/saving code
68 void addTag (char[] tp, ID id, char[] dt) { 232 void addTag (char[] tp, ID id, char[] dt) {
69 if (tp == "bool") { 233 mixin(addTagMixin!(TYPES).addTagMixin);
70 bool** p = id in optsBool;
71 if (p !is null) **p = parseTo!(bool) (dt);
72 } else if (tp == "char[]") {
73 char[]** p = id in optsCharA;
74 if (p !is null) **p = parseTo!(char[]) (dt);
75 } else if (tp == "double") {
76 double** p = id in optsDouble;
77 if (p !is null) **p = parseTo!(double) (dt);
78 } else if (tp == "int") {
79 int** p = id in optsInt;
80 if (p !is null) **p = parseTo!(int) (dt);
81 }
82 } 234 }
83 235
84 void writeAll (ItemDelg dlg) { 236 void writeAll (ItemDelg dlg) {
85 foreach (ID id, bool* val; optsBool) dlg ("bool" , id, parseFrom!(bool ) (*val)); 237 mixin(writeAllMixin!(TYPES));
86 foreach (ID id, char[]* val; optsCharA) dlg ("char[]", id, parseFrom!(char[]) (*val));
87 foreach (ID id, double* val; optsDouble)dlg ("double", id, parseFrom!(double) (*val));
88 foreach (ID id, int* val; optsInt) dlg ("int" , id, parseFrom!(int ) (*val));
89 } 238 }
90 //END Mergetag loading/saving code 239 //END Mergetag loading/saving code
91 240 //END Non-static
92 //BEGIN Static 241
93 /** Add an options sub-class to the list for loading and saving. 242
94 * 243 //BEGIN Templates: impl & optionsThis
95 * Call from static this() (before Init calls load()). */
96 static void addOptionsClass (Options c, char[] i)
97 in { // Trap a couple of potential coding errors:
98 assert (c !is null); // Instance must be created before calling addOptionsClass
99 assert (((cast(ID) i) in subClasses) is null); // Don't allow a silent replacement
100 } body {
101 subClasses[cast(ID) i] = c;
102 subClassChanges[cast(ID) i] = new OptionsGeneric;
103 }
104
105 /** Set option symbol of Options class subClass to val.
106 *
107 * Due to the way options are handled generically, string IDs must be used to access the options
108 * via hash-maps, which is a little slower than direct access but necessary since the option
109 * must be changed in two separate places. */
110 private static const ERR_MSG = "Options.setXXX called with incorrect parameters!";
111 static void setBool (char[] subClass, char[] symbol, bool val) {
112 changed = true; // something got set (don't bother checking this isn't what it already was)
113
114 try {
115 *(subClasses[cast(ID) subClass].optsBool[cast(ID) symbol]) = val;
116 subClassChanges[cast(ID) subClass].setBool (cast(ID) symbol, val);
117 } catch (ArrayBoundsException) {
118 // log and ignore:
119 logger.error (ERR_MSG);
120 }
121 }
122 static void setInt (char[] subClass, char[] symbol, int val) {
123 changed = true; // something got set (don't bother checking this isn't what it already was)
124
125 try {
126 *(subClasses[cast(ID) subClass].optsInt[cast(ID) symbol]) = val;
127 subClassChanges[cast(ID) subClass].setInt (cast(ID) symbol, val);
128 } catch (ArrayBoundsException) {
129 // log and ignore:
130 logger.error (ERR_MSG);
131 }
132 }
133 static void setDouble (char[] subClass, char[] symbol, double val) {
134 changed = true; // something got set (don't bother checking this isn't what it already was)
135
136 try {
137 *(subClasses[cast(ID) subClass].optsDouble[cast(ID) symbol]) = val;
138 subClassChanges[cast(ID) subClass].setDouble (cast(ID) symbol, val);
139 } catch (ArrayBoundsException) {
140 // log and ignore:
141 logger.error (ERR_MSG);
142 }
143 }
144 static void setCharA (char[] subClass, char[] symbol, char[] val) {
145 changed = true; // something got set (don't bother checking this isn't what it already was)
146
147 try {
148 *(subClasses[cast(ID) subClass].optsCharA[cast(ID) symbol]) = val;
149 subClassChanges[cast(ID) subClass].setCharA (cast(ID) symbol, val);
150 } catch (ArrayBoundsException) {
151 // log and ignore:
152 logger.error (ERR_MSG);
153 }
154 }
155
156 // Track all sections for saving/loading/other generic handling.
157 static Options[ID] subClasses;
158 static OptionsGeneric[ID] subClassChanges;
159 static bool changed = false; // any changes at all, i.e. do we need to save?
160
161 /* Load/save options from file.
162 *
163 * If the file doesn't exist, no reading is attempted (options are left at default values).
164 */
165 private static const fileName = "options";
166 private static const MT_LOAD_EXC = "Loading options aborted:";
167 static void load () {
168 // Check it exists (if not it should still be created on exit).
169 // Don't bother checking it's not a folder, because it could still be a block or something.
170 if (!confDir.exists (fileName)) return;
171
172 try {
173 IReader reader;
174 reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
175 reader.dataSecCreator = delegate IDataSection(ID id) {
176 /* Recognise each defined section, and return null for unrecognised sections. */
177 Options* p = id in subClasses;
178 if (p !is null) return *p;
179 else return null;
180 };
181 reader.read;
182 } catch (MTException e) {
183 logger.fatal (MT_LOAD_EXC);
184 logger.fatal (e.msg);
185 throw new optionsLoadException ("Mergetag exception (see above message)");
186 }
187 }
188 static void save () {
189 if (!changed) return; // no changes to save
190
191 DataSet ds = new DataSet();
192 foreach (id, sec; subClassChanges) ds.sec[id] = sec;
193
194 // Read locally-stored options
195 try {
196 IReader reader;
197 reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, ds);
198 reader.dataSecCreator = delegate IDataSection(ID id) {
199 return null; // All recognised sections are already in the dataset.
200 };
201 reader.read;
202 } catch (MTFileIOException) {
203 // File either didn't exist or couldn't be opened.
204 // Presuming the former, this is not a problem.
205 } catch (MTException e) {
206 // Log a message and continue, overwriting the file:
207 logger.error (MT_LOAD_EXC);
208 logger.error (e.msg);
209 }
210
211 try {
212 IWriter writer;
213 writer = confDir.makeMTWriter (fileName, ds);
214 writer.write();
215 } catch (MTException e) {
216 logger.error ("Saving options aborted! Reason:");
217 logger.error (e.msg);
218 }
219 }
220
221 private static Logger logger;
222 static this() {
223 logger = Log.getLogger ("mde.options");
224 }
225 //END Static
226
227 //BEGIN Templates
228 private { 244 private {
229 // Return index of first comma, or halts if not found. 245 // Return index of first comma, or halts if not found.
230 template cIndex(char[] A) { 246 template cIndex(char[] A) {
231 static if (A.length == 0) 247 static if (A.length == 0)
232 static assert (false, "Error in implementation"); 248 static assert (false, "Error in implementation");
282 else static if (A[0] == ' ') 298 else static if (A[0] == ' ')
283 const char[] listOrNull = listOrNull!(A[1..$]); 299 const char[] listOrNull = listOrNull!(A[1..$]);
284 else 300 else
285 const char[] listOrNull = "["~A~"]"; 301 const char[] listOrNull = "["~A~"]";
286 } 302 }
303 // for recursing on TYPES
304 template optionsThisInternal(char[] A, B...) {
305 static if (B.length) {
306 const char[] optionsThisInternal = `opts`~TName!(B[0])~` = `~listOrNull!(sTC!(aaVars!(parseT!(B[0].stringof,A))))~";\n" ~ optionsThisInternal!(A,B[1..$]);
307 } else
308 const char[] optionsThisInternal = ``;
309 }
287 } protected { 310 } protected {
288 /** Produces the implementation code to go in the constuctor. */ 311 /** Produces the implementation code to go in the constuctor. */
289 template aaDefs(char[] A) { 312 template optionsThis(char[] A) {
290 const char[] aaDefs = 313 const char[] optionsThis =
291 "optsBool = " ~ listOrNull!(sTC!(aaVars!(parseT!("bool" , A)))) ~ ";\n" ~ 314 "optionChanges = new OptionChanges;\n" ~
292 "optsInt = " ~ listOrNull!(sTC!(aaVars!(parseT!("int" , A)))) ~ ";\n" ~ 315 optionsThisInternal!(A,TYPES);
293 "optsDouble = "~ listOrNull!(sTC!(aaVars!(parseT!("double", A)))) ~ ";\n" ~ 316 }
294 "optsCharA = " ~ listOrNull!(sTC!(aaVars!(parseT!("char[]", A)))) ~ ";\n"; 317 /+ Needs too many custom parameters to be worth it? Plus makes class less readable.
295 } 318 /** Produces the implementation code to go in the static constuctor. */
296 /+/** Produces the implementation code to go in the static constuctor. */
297 template optClassAdd(char[] symb) { 319 template optClassAdd(char[] symb) {
298 const char[] optClassAdd = symb ~ "=new "~classinfo(this).name~";\n";//Options.addOptionsClass("~symb~", );\n"; 320 const char[] optClassAdd = symb ~ "=new "~classinfo(this).name~";\n";//Options.addOptionsClass("~symb~", );\n";
299 }+/ 321 }+/
300 /** mixin impl("type symbol[, symbol[...]];[type symbol[...];][...]") 322 /** mixin impl("type symbol[, symbol[...]];[type symbol[...];][...]")
301 * 323 *
304 * mixin (impl ("bool a, b; int i;")); 326 * mixin (impl ("bool a, b; int i;"));
305 * --- 327 * ---
306 * 328 *
307 * In case this() needs to be customized, mixin(impl!(A)) is equivalent to: 329 * In case this() needs to be customized, mixin(impl!(A)) is equivalent to:
308 * --- 330 * ---
309 * mixin (A~"\nthis(){\n"~aaDefs!(A)~"}"); 331 * mixin (A~`
332 this () {
333 `~optionsThis!(A)~`
334 }`);
310 * --- 335 * ---
311 * 336 *
312 * Notes: Only use space as whitespace (no new-lines or tabs). Make sure to add a trailing 337 * Notes: Only use space as whitespace (no new-lines or tabs). Make sure to add a trailing
313 * semi-colon (;) or you'll get told off! :D 338 * semi-colon (;) or you'll get told off! :D
314 *
315 * In general errors aren't reported well. Trial with pragma (msg, impl!(...)); if 339 * In general errors aren't reported well. Trial with pragma (msg, impl!(...)); if
316 * necessary. 340 * necessary.
317 * 341 *
318 * Extending: mixins could also be used for the static this() {...} or even the whole 342 * Extending: mixins could also be used for the static this() {...} or even the whole
319 * class, but doing so would rather decrease readability of any implementation. */ 343 * class, but doing so would rather decrease readability of any implementation. */
320 template impl(char[] A /+, char[] symb+/) { 344 template impl(char[] A /+, char[] symb+/) {
321 const char[] impl = A~"\nthis(){\n"~aaDefs!(A)~"}"; 345 const char[] impl = A~"\nthis(){\n"~optionsThis!(A)~"}";
322 // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}" 346 // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}"
323 } 347 }
324 } 348 }
325 /+/** mixin impl("type symbol[, symbol[...]];[type symbol[...];][...]") 349 //END Templates: impl & optionsThis
326 * 350 }
327 * E.g. 351
328 * --- 352 /* Special class to store all locally changed options, whatever the section. */
329 * mixin (impl ("bool a, b; int i;")); 353 class OptionChanges : Options {
330 * --- 354 //BEGIN Templates
331 * The parser isn't highly accurate. */ 355 private {
332 // Try using templates instead? See std.metastrings 356 template Vars(A...) {
333 static char[] impl (char[] A) { 357 static if (A.length) {
334 char[] bools; 358 const char[] Vars = A[0].stringof~`[] `~TName!(A[0])~`s;` ~ Vars!(A[1..$]);
335 char[] ints; 359 } else
336 360 const char[] Vars = ``;
337 while (A.length) { 361 }
338 // Trim whitespace 362
339 { 363 // For addTag; different to Options.addTag().
340 size_t i = 0; 364 // Reverse priority: only load symbols not currently existing
341 while (i < A.length && (A[i] == ' ' || (A[i] >= 9u && A[i] <= 0xD))) 365 template addTagMixin(T, A...) {
342 ++i; 366 const char[] ifBlock = `if (tp == "`~T.stringof~`") {
343 A = A[i..$]; 367 if ((id in opts`~TName!(T)~`) is null) {
344 } 368 `~TName!(T)~`s ~= parseTo!(`~T.stringof~`) (dt);
345 if (A.length == 0) break; 369 opts`~TName!(T)~`[id] = &`~TName!(T)~`s[$-1];
346 370 }
347 char[] type; 371 }`;
348 for (size_t i = 0; i < A.length; ++i) { 372 static if (A.length)
349 if (A[i] == ' ' || (A[i] >= 9u && A[i] <= 0xD)) { 373 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin;
350 type = A[0..i]; 374 else
351 A = A[i+1..$]; 375 const char[] addTagMixin = ifBlock;
352 break; 376 }
353 } 377
354 } 378 }
355
356 char[] symbols;
357 for (size_t i = 0; i < A.length; ++i) {
358 if (A[i] == ';') {
359 symbols = A[0..i];
360 A = A[i+1..$];
361 break;
362 }
363 }
364
365 if (type == "bool") {
366 if (bools.length)
367 bools = bools ~ "," ~ symbols;
368 else
369 bools = symbols;
370 }
371 else if (type == "int") {
372 if (ints.length)
373 ints = ints ~ "," ~ symbols;
374 else
375 ints = symbols;
376 }
377 else {
378 // Unfortunately, we cannot output non-const strings (even though func is compile-time)
379 // We also cannot use pragma(msg) because the message gets printed even if the code isn't run.
380 //pragma(msg, "Warning: impl failed to parse whole input string");
381 // Cannot use Cout / logger either.
382 break;
383 }
384 }
385
386 char[] ret;
387 if (bools.length)
388 ret = "bool "~bools~";\n";
389 if (ints.length)
390 ret = ret ~ "int "~ints~";\n";
391
392
393
394 return ret;
395 }+/
396 //END Templates 379 //END Templates
397 }
398
399 /* Special class to store all locally changed options, whatever the section. */
400 class OptionsGeneric : Options {
401 // These store the actual values, but are never accessed directly except when initially added. 380 // These store the actual values, but are never accessed directly except when initially added.
402 // optsX store pointers to each item added along with the ID and are used for access. 381 // optsX store pointers to each item added along with the ID and are used for access.
403 bool[] bools; 382 mixin(Vars!(TYPES));
404 int[] ints;
405 double[] doubles;
406 char[][] strings;
407 383
408 this () {} 384 this () {}
409 385
410 void setBool (ID id, bool x) { 386 void set(T) (ID id, T x) {
411 bool** p = id in optsBool; 387 static if (!TIsIn!(T,TYPES))
388 static assert (false, "OptionChanges.set does not currently support type "~T.stringof);
389
390 mixin (`alias opts`~T.stringof~` optsVars;`);
391 mixin (`alias `~T.stringof~`s vars;`);
392
393 T** p = id in optsVars;
412 if (p !is null) **p = x; 394 if (p !is null) **p = x;
413 else { 395 else {
414 bools ~= x; 396 vars ~= x;
415 optsBool[id] = &bools[$-1]; 397 optsVars[id] = &vars[$-1];
416 }
417 }
418 void setInt (ID id, int x) {
419 int** p = id in optsInt;
420 if (p !is null) **p = x;
421 else {
422 ints ~= x;
423 optsInt[id] = &ints[$-1];
424 }
425 }
426 void setDouble (ID id, double x) {
427 double** p = id in optsDouble;
428 if (p !is null) **p = x;
429 else {
430 doubles ~= x;
431 optsDouble[id] = &doubles[$-1];
432 }
433 }
434 void setCharA (ID id, char[] x) {
435 char[]** p = id in optsCharA;
436 if (p !is null) **p = x;
437 else {
438 strings ~= x;
439 optsCharA[id] = &strings[$-1];
440 } 398 }
441 } 399 }
442 400
443 //BEGIN Mergetag loading/saving code 401 //BEGIN Mergetag loading/saving code
444 // Reverse priority: only load symbols not currently existing 402 // Reverse priority: only load symbols not currently existing
445 void addTag (char[] tp, ID id, char[] dt) { 403 void addTag (char[] tp, ID id, char[] dt) {
446 if (tp == "bool") { 404 mixin (addTagMixin!(TYPES).addTagMixin);
447 if ((id in optsBool) is null) {
448 bools ~= parseTo!(bool) (dt);
449 optsBool[id] = &bools[$-1];
450 }
451 } else if (tp == "char[]") {
452 if ((id in optsCharA) is null) {
453 strings ~= parseTo!(char[]) (dt);
454 optsCharA[id] = &strings[$-1];
455 }
456 char[]** p = id in optsCharA;
457 if (p !is null) **p = parseTo!(char[]) (dt);
458 } else if (tp == "int") {
459 if ((id in optsInt) is null) {
460 ints ~= parseTo!(int) (dt);
461 optsInt[id] = &ints[$-1];
462 }
463 }
464 } 405 }
465 //END Mergetag loading/saving code 406 //END Mergetag loading/saving code
466 } 407 }
467 408
468 /* NOTE: Options sub-classes are expected to use a template to ease inserting contents and 409 /* NOTE: Options sub-classes are expected to use a template to ease inserting contents and