1 /** 2 * parse configuration options 3 * 4 * Copyright: Copyright Digital Mars 2017 5 * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 * 7 * Source: $(DRUNTIMESRC core/internal/parseoptions.d) 8 */ 9 10 module core.internal.parseoptions; 11 12 import core.stdc.stdlib; 13 import core.stdc.stdio; 14 import core.stdc.ctype; 15 import core.stdc.string; 16 import core.vararg; 17 import core.internal.traits : externDFunc, hasUDA; 18 19 20 @nogc nothrow: 21 extern extern(C) string[] rt_args() @system; 22 23 extern extern(C) __gshared bool rt_envvars_enabled; 24 extern extern(C) __gshared bool rt_cmdline_enabled; 25 extern extern(C) __gshared string[] rt_options; 26 27 alias rt_configCallBack = string delegate(string) @nogc nothrow; 28 alias fn_configOption = string function(string opt, scope rt_configCallBack dg, bool reverse) @nogc nothrow; 29 alias rt_configOption = externDFunc!("rt.config.rt_configOption", fn_configOption); 30 31 /// UDA for field treated as memory value 32 struct MemVal {} 33 34 /** 35 * initialize members of struct CFG from rt_config options 36 * 37 * options will be read from the environment, the command line or embedded 38 * into the executable as configured (see rt.config) 39 * 40 * fields of the struct are populated by parseOptions(). 41 */ 42 bool initConfigOptions(CFG)(ref CFG cfg, string cfgname) 43 { 44 string parse(string opt) @nogc nothrow 45 { 46 if (!parseOptions(cfg, opt)) 47 return "err"; 48 return null; // continue processing 49 } 50 string s = rt_configOption(cfgname, &parse, true); 51 return s is null; 52 } 53 54 /** 55 * initialize members of struct CFG from a string of sub-options. 56 * 57 * fields of the struct are populated by listing them as space separated 58 * sub-options <field-name>:value, e.g. "precise:1 profile:1" 59 * 60 * supported field value types: 61 * - strings (without spaces) 62 * - integer types (positive values only) 63 * - bool 64 * - float 65 * 66 * If the struct has a member "help" it is called if it is found as a sub-option. 67 * If the struct has a member "errorName", is used as the name reported in error 68 * messages. Otherwise the struct name is used. 69 */ 70 bool parseOptions(CFG)(ref CFG cfg, string opt) 71 { 72 static if (is(typeof(__traits(getMember, CFG, "errorName")))) 73 string errName = cfg.errorName; 74 else 75 string errName = CFG.stringof; 76 opt = skip!isspace(opt); 77 while (opt.length) 78 { 79 auto tail = find!(c => c == ':' || c == '=' || c == ' ')(opt); 80 auto name = opt[0 .. $ - tail.length]; 81 static if (is(typeof(__traits(getMember, CFG, "help")))) 82 if (name == "help") 83 { 84 version (CoreUnittest) {} else 85 cfg.help(); 86 opt = skip!isspace(tail); 87 continue; 88 } 89 if (tail.length <= 1 || tail[0] == ' ') 90 return optError("Missing argument for", name, errName); 91 tail = tail[1 .. $]; 92 93 NAMES_SWITCH: 94 switch (name) 95 { 96 static foreach (field; __traits(allMembers, CFG)) 97 { 98 static if (!is(typeof(__traits(getMember, cfg, field)) == function)) 99 { 100 case field: 101 bool r; 102 103 static if (hasUDA!(__traits(getMember, cfg, field), MemVal)) 104 r = parse(name, tail, __traits(getMember, cfg, field), errName, true); 105 else 106 r = parse(name, tail, __traits(getMember, cfg, field), errName); 107 108 if (!r) 109 return false; 110 111 break NAMES_SWITCH; 112 } 113 } 114 115 default: 116 return optError("Unknown", name, errName); 117 } 118 opt = skip!isspace(tail); 119 } 120 return true; 121 } 122 123 /** 124 Parses an individual option `optname` value from a provided string `str`. 125 The option type is given by the type `T` of the field `res` to which the parsed 126 value will be written too. 127 In case of an error, `errName` will be used to display an error message and 128 the failure of the parsing will be indicated by a falsy return value. 129 130 For boolean values, '0/n/N' (false) or '1/y/Y' (true) are accepted. 131 132 Params: 133 optname = name of the option to parse 134 str = raw string to parse the option value from 135 res = reference to the resulting data field that the option should be parsed too 136 errName = full-text name of the option which should be displayed in case of errors 137 138 Returns: `false` if a parsing error happened. 139 */ 140 bool rt_parseOption(T)(const(char)[] optname, ref inout(char)[] str, ref T res, const(char)[] errName) 141 { 142 return parse(optname, str, res, errName); 143 } 144 145 private: 146 147 bool optError(const scope char[] msg, const scope char[] name, const(char)[] errName) 148 { 149 version (CoreUnittest) if (inUnittest) return false; 150 151 fprintf(stderr, "%.*s %.*s option '%.*s'.\n", 152 cast(int)msg.length, msg.ptr, 153 cast(int)errName.length, errName.ptr, 154 cast(int)name.length, name.ptr); 155 return false; 156 } 157 158 inout(char)[] skip(alias pred)(inout(char)[] str) 159 { 160 return find!(c => !pred(c))(str); 161 } 162 163 inout(char)[] find(alias pred)(inout(char)[] str) 164 { 165 foreach (i; 0 .. str.length) 166 if (pred(str[i])) return str[i .. $]; 167 return null; 168 } 169 170 bool parse(T : size_t)(const(char)[] optname, ref inout(char)[] str, ref T res, const(char)[] errName, bool mayHaveSuffix = false) 171 in { assert(str.length); } 172 do 173 { 174 size_t i, v; 175 176 auto tail = find!(c => c == ' ')(str); 177 size_t len = str.length - tail.length; 178 179 import core.checkedint : mulu; 180 181 bool overflowed; 182 183 for (; i < len; i++) 184 { 185 char c = str[i]; 186 187 if (isdigit(c)) 188 v = 10 * v + c - '0'; 189 else // non-digit 190 { 191 if (mayHaveSuffix && i == len-1) // suffix 192 { 193 switch (c) 194 { 195 196 case 'G': 197 v = mulu(v, 1024 * 1024 * 1024, overflowed); 198 break; 199 200 case 'M': 201 v = mulu(v, 1024 * 1024, overflowed); 202 break; 203 204 case 'K': 205 v = mulu(v, 1024, overflowed); 206 break; 207 208 case 'B': 209 break; 210 211 default: 212 return parseError("value with unit type M, K or B", optname, str, "with suffix"); 213 } 214 215 if (overflowed) 216 return overflowedError(optname, str); 217 218 i++; 219 break; 220 } 221 else // unexpected non-digit character 222 { 223 i = 0; 224 break; 225 } 226 } 227 } 228 229 if (!i) 230 return parseError("a number", optname, str, errName); 231 232 if (mayHaveSuffix && isdigit(str[len-1])) 233 { 234 // No suffix found, default to megabytes 235 236 v = mulu(v, 1024 * 1024, overflowed); 237 238 if (overflowed) 239 return overflowedError(optname, str); 240 } 241 242 if (v > res.max) 243 return parseError("a number " ~ T.max.stringof ~ " or below", optname, str[0 .. i], errName); 244 str = str[i .. $]; 245 res = cast(T) v; 246 return true; 247 } 248 249 bool parse(const(char)[] optname, ref inout(char)[] str, ref bool res, const(char)[] errName) 250 in { assert(str.length); } 251 do 252 { 253 if (str[0] == '1' || str[0] == 'y' || str[0] == 'Y') 254 res = true; 255 else if (str[0] == '0' || str[0] == 'n' || str[0] == 'N') 256 res = false; 257 else 258 return parseError("'0/n/N' or '1/y/Y'", optname, str, errName); 259 str = str[1 .. $]; 260 return true; 261 } 262 263 bool parse(const(char)[] optname, ref inout(char)[] str, ref float res, const(char)[] errName) 264 in { assert(str.length); } 265 do 266 { 267 // % uint f %n \0 268 char[1 + 10 + 1 + 2 + 1] fmt=void; 269 // specify max-width 270 immutable n = snprintf(fmt.ptr, fmt.length, "%%%uf%%n", cast(uint)str.length); 271 assert(n > 4 && n < fmt.length); 272 273 int nscanned; 274 version (CRuntime_DigitalMars) 275 { 276 /* Older sscanf's in snn.lib can write to its first argument, causing a crash 277 * if the string is in readonly memory. Recent updates to DMD 278 * https://github.com/dlang/dmd/pull/6546 279 * put string literals in readonly memory. 280 * Although sscanf has been fixed, 281 * http://ftp.digitalmars.com/snn.lib 282 * this workaround is here so it still works with the older snn.lib. 283 */ 284 // Create mutable copy of str 285 const length = str.length; 286 char* mptr = cast(char*)malloc(length + 1); 287 assert(mptr); 288 memcpy(mptr, str.ptr, length); 289 mptr[length] = 0; 290 const result = sscanf(mptr, fmt.ptr, &res, &nscanned); 291 free(mptr); 292 if (result < 1) 293 return parseError("a float", optname, str, errName); 294 } 295 else 296 { 297 if (sscanf(str.ptr, fmt.ptr, &res, &nscanned) < 1) 298 return parseError("a float", optname, str, errName); 299 } 300 str = str[nscanned .. $]; 301 return true; 302 } 303 304 bool parse(const(char)[] optname, ref inout(char)[] str, ref inout(char)[] res, const(char)[] errName) 305 in { assert(str.length); } 306 do 307 { 308 auto tail = str.find!(c => c == ' '); 309 res = str[0 .. $ - tail.length]; 310 if (!res.length) 311 return parseError("an identifier", optname, str, errName); 312 str = tail; 313 return true; 314 } 315 316 bool parseError(const scope char[] exp, const scope char[] opt, const scope char[] got, const(char)[] errName) 317 { 318 version (CoreUnittest) if (inUnittest) return false; 319 320 fprintf(stderr, "Expecting %.*s as argument for %.*s option '%.*s', got '%.*s' instead.\n", 321 cast(int)exp.length, exp.ptr, 322 cast(int)errName.length, errName.ptr, 323 cast(int)opt.length, opt.ptr, 324 cast(int)got.length, got.ptr); 325 return false; 326 } 327 328 bool overflowedError(const scope char[] opt, const scope char[] got) 329 { 330 version (CoreUnittest) if (inUnittest) return false; 331 332 fprintf(stderr, "Argument for %.*s option '%.*s' is too big.\n", 333 cast(int)opt.length, opt.ptr, 334 cast(int)got.length, got.ptr); 335 return false; 336 } 337 338 size_t min(size_t a, size_t b) { return a <= b ? a : b; } 339 340 version (CoreUnittest) __gshared bool inUnittest; 341 342 unittest 343 { 344 inUnittest = true; 345 scope (exit) inUnittest = false; 346 347 static struct Config 348 { 349 bool disable; // start disabled 350 ubyte profile; // enable profiling with summary when terminating program 351 string gc = "conservative"; // select gc implementation conservative|manual 352 353 @MemVal size_t initReserve; // initial reserve (bytes) 354 @MemVal size_t minPoolSize = 1 << 20; // initial and minimum pool size (bytes) 355 float heapSizeFactor = 2.0; // heap size to used memory ratio 356 357 @nogc nothrow: 358 void help(); 359 string errorName() @nogc nothrow { return "GC"; } 360 } 361 Config conf; 362 363 assert(!conf.parseOptions("disable")); 364 assert(!conf.parseOptions("disable:")); 365 assert(!conf.parseOptions("disable:5")); 366 assert(conf.parseOptions("disable:y") && conf.disable); 367 assert(conf.parseOptions("disable:n") && !conf.disable); 368 assert(conf.parseOptions("disable:Y") && conf.disable); 369 assert(conf.parseOptions("disable:N") && !conf.disable); 370 assert(conf.parseOptions("disable:1") && conf.disable); 371 assert(conf.parseOptions("disable:0") && !conf.disable); 372 373 assert(conf.parseOptions("disable=y") && conf.disable); 374 assert(conf.parseOptions("disable=n") && !conf.disable); 375 376 assert(conf.parseOptions("profile=0") && conf.profile == 0); 377 assert(conf.parseOptions("profile=1") && conf.profile == 1); 378 assert(conf.parseOptions("profile=2") && conf.profile == 2); 379 assert(!conf.parseOptions("profile=256")); 380 381 assert(conf.parseOptions("disable:1 minPoolSize:16")); 382 assert(conf.disable); 383 assert(conf.minPoolSize == 1024 * 1024 * 16); 384 385 assert(conf.parseOptions("disable:1 minPoolSize:4096B")); 386 assert(conf.disable); 387 assert(conf.minPoolSize == 4096); 388 389 assert(conf.parseOptions("disable:1 minPoolSize:2K help")); 390 assert(conf.disable); 391 assert(conf.minPoolSize == 2048); 392 393 assert(conf.parseOptions("minPoolSize:3G help")); 394 assert(conf.disable); 395 assert(conf.minPoolSize == 1024UL * 1024 * 1024 * 3); 396 397 assert(!conf.parseOptions("minPoolSize:922337203685477G"), "size_t overflow"); 398 399 assert(conf.parseOptions("heapSizeFactor:3.1")); 400 assert(conf.heapSizeFactor == 3.1f); 401 assert(conf.parseOptions("heapSizeFactor:3.1234567890 disable:0")); 402 assert(conf.heapSizeFactor > 3.123f); 403 assert(!conf.disable); 404 assert(!conf.parseOptions("heapSizeFactor:3.0.2.5")); 405 assert(conf.parseOptions("heapSizeFactor:2")); 406 assert(conf.heapSizeFactor == 2.0f); 407 408 assert(!conf.parseOptions("initReserve:foo")); 409 assert(!conf.parseOptions("initReserve:y")); 410 assert(!conf.parseOptions("initReserve:20.5")); 411 412 assert(conf.parseOptions("help")); 413 assert(conf.parseOptions("help profile:1")); 414 assert(conf.parseOptions("help profile:1 help")); 415 416 assert(conf.parseOptions("gc:manual") && conf.gc == "manual"); 417 assert(conf.parseOptions("gc:my-gc~modified") && conf.gc == "my-gc~modified"); 418 assert(conf.parseOptions("gc:conservative help profile:1") && conf.gc == "conservative" && conf.profile == 1); 419 420 // the config parse doesn't know all available GC names, so should accept unknown ones 421 assert(conf.parseOptions("gc:whatever")); 422 } 423