diff --git a/driver/config.d b/driver/config.d index 46990a49a93..b368aa9984a 100644 --- a/driver/config.d +++ b/driver/config.d @@ -66,10 +66,11 @@ class ScalarSetting : Setting class ArraySetting : Setting { - this(string name, string[] vals) + this(string name, string[] vals, bool isAppending) { super(name, Type.array); _vals = vals; + _isAppending = isAppending; } @property const(string)[] vals() const @@ -77,7 +78,13 @@ class ArraySetting : Setting return _vals; } + @property bool isAppending() const + { + return _isAppending; + } + private string[] _vals; + private bool _isAppending; } class GroupSetting : Setting @@ -133,7 +140,7 @@ EBNF grammar. It is a subset of the libconfig grammar (http://www.hyperrealm.com/libconfig). config = { ows , setting } , ows ; -setting = (name | string) , (":" | "=") , value , [";" | ","] ; +setting = (name | string) , (":" | "=" | "~=") , value , [";" | ","] ; name = alpha , { alpha | digit | "_" | "-" } ; value = string | array | group ; array = "[" , ows , @@ -172,6 +179,7 @@ enum Token { name, assign, // ':' or '=' + appendAssign, // '~=' str, lbrace, // '{' rbrace, // '}' @@ -187,17 +195,18 @@ string humanReadableToken(in Token tok) { final switch(tok) { - case Token.name: return `"name"`; - case Token.assign: return `':' or '='`; - case Token.str: return `"string"`; - case Token.lbrace: return `'{'`; - case Token.rbrace: return `'}'`; - case Token.lbracket: return `'['`; - case Token.rbracket: return `']'`; - case Token.semicolon: return `';'`; - case Token.comma: return `','`; - case Token.unknown: return `"unknown token"`; - case Token.eof: return `"end of file"`; + case Token.name: return `"name"`; + case Token.assign: return `':' or '='`; + case Token.appendAssign: return `'~='`; + case Token.str: return `"string"`; + case Token.lbrace: return `'{'`; + case Token.rbrace: return `'}'`; + case Token.lbracket: return `'['`; + case Token.rbracket: return `']'`; + case Token.semicolon: return `';'`; + case Token.comma: return `','`; + case Token.unknown: return `"unknown token"`; + case Token.eof: return `"end of file"`; } } @@ -226,11 +235,14 @@ struct Parser void error(in string msg) { - enum fmt = "Error while reading config file: %.*s\nline %d: %.*s"; - char[1024] buf; - auto len = snprintf(buf.ptr, buf.length, fmt, cast(int) filename.length, - filename.ptr, lineNum, cast(int) msg.length, msg.ptr); - throw new Exception(buf[0 .. len].idup); + error(msg, lineNum); + } + + void error(in string msg, int lineNum) + { + char[20] buf = void; + auto len = snprintf(buf.ptr, buf.length, "line %d: ", lineNum); + throw new Exception((cast(string) buf[0 .. len]) ~ msg); } char getChar() @@ -275,6 +287,19 @@ struct Parser return getTok(outStr); } + if (lastChar == '~') + { + lastChar = getChar(); + if (lastChar != '=') + { + outStr = "~"; + return Token.unknown; + } + + lastChar = getChar(); + return Token.appendAssign; + } + if (isalpha(lastChar)) { string name; @@ -410,17 +435,6 @@ struct Parser ". Got " ~ humanReadableToken(tok) ~ s ~ " instead."); } - string accept(in Token expected) - { - string s; - immutable tok = getTok(s); - if (tok != expected) - { - unexpectedTokenError(tok, expected, s); - } - return s; - } - Setting[] parseConfig() { Setting[] res; @@ -450,11 +464,29 @@ struct Parser assert(false); } - accept(Token.assign); + string s; + t = getTok(s); + if (t != Token.assign && t != Token.appendAssign) + { + auto msg = "Expected either" + ~ " token " ~ humanReadableToken(Token.assign) + ~ " or token " ~ humanReadableToken(Token.appendAssign) + ~ " but got: " ~ humanReadableToken(t) + ~ ' ' ~ (s.length ? '(' ~ s ~ ')' : s); + error(msg); + } + // This is off by +1 if `t` is followed by \n + const assignLineNum = lineNum; - Setting res = parseValue(name); + Setting res = parseValue(name, t); + if (t == Token.appendAssign) + { + if (res.type == Setting.Type.scalar) + error(humanReadableToken(t) ~ " is not supported with scalar values", assignLineNum); + if (res.type == Setting.Type.group) + error(humanReadableToken(t) ~ " is not supported with groups", assignLineNum); + } - string s; t = getTok(s); if (t != Token.semicolon && t != Token.comma) { @@ -464,8 +496,10 @@ struct Parser return res; } - Setting parseValue(string name) + Setting parseValue(string name, Token tAssign = Token.assign) { + assert(tAssign == Token.assign || tAssign == Token.appendAssign); + string s; auto t = getTok(s); if (t == Token.str) @@ -474,6 +508,7 @@ struct Parser } else if (t == Token.lbracket) { + const isAppending = tAssign == Token.appendAssign; string[] arrVal; while (1) { @@ -485,7 +520,7 @@ struct Parser arrVal ~= s; break; case Token.rbracket: - return new ArraySetting(name, arrVal); + return new ArraySetting(name, arrVal, isAppending); default: unexpectedTokenError(t, Token.str, s); assert(false); @@ -498,7 +533,7 @@ struct Parser case Token.comma: break; case Token.rbracket: - return new ArraySetting(name, arrVal); + return new ArraySetting(name, arrVal, isAppending); default: unexpectedTokenError(t, Token.comma, s); assert(false); @@ -578,6 +613,8 @@ group-1_2: {}; scalar = "abc"; // comment Array_1-2 = [ "a" ]; + + AppArray ~= [ "x" ]; // appending array }; `; @@ -591,7 +628,7 @@ group-1_2: {}; assert(settings[1].name == "86(_64)?-.*linux\\.?"); assert(settings[1].type == Setting.Type.group); auto group2 = cast(GroupSetting) settings[1]; - assert(group2.children.length == 2); + assert(group2.children.length == 3); assert(group2.children[0].name == "scalar"); assert(group2.children[0].type == Setting.Type.scalar); @@ -600,4 +637,10 @@ group-1_2: {}; assert(group2.children[1].name == "Array_1-2"); assert(group2.children[1].type == Setting.Type.array); assert((cast(ArraySetting) group2.children[1]).vals == [ "a" ]); + assert((cast(ArraySetting) group2.children[1]).isAppending == false); + + assert(group2.children[2].name == "AppArray"); + assert(group2.children[2].type == Setting.Type.array); + assert((cast(ArraySetting) group2.children[2]).vals == [ "x" ]); + assert((cast(ArraySetting) group2.children[2]).isAppending == true); } diff --git a/driver/configfile.d b/driver/configfile.d index c2be290afc2..11c7299416b 100644 --- a/driver/configfile.d +++ b/driver/configfile.d @@ -14,15 +14,14 @@ module driver.configfile; import dmd.globals; import dmd.root.array; +import dmd.root.string : toDString, toCString, toCStringThen; import driver.config; import core.stdc.stdio; -import core.stdc.string; string normalizeSlashes(const(char)* binDir) { - immutable len = strlen(binDir); - auto res = binDir[0 .. len].dup; + auto res = binDir.toDString.dup; foreach (ref c; res) { if (c == '\\') c = '/'; @@ -30,28 +29,38 @@ string normalizeSlashes(const(char)* binDir) return cast(string)res; // assumeUnique } -T findSetting(T)(GroupSetting[] sections, Setting.Type type, string name) +const(string)[] findArraySetting(GroupSetting[] sections, string name) { - // lexically later sections dominate earlier ones - foreach_reverse (section; sections) + const(string)[] result = null; + foreach (section; sections) { foreach (c; section.children) { - if (c.type == type && c.name == name) - return cast(T) c; + if (c.type == Setting.Type.array && c.name == name) + { + auto as = cast(ArraySetting) c; + if (as.isAppending) + result ~= as.vals; + else + result = as.vals; + } } } - return null; -} - -ArraySetting findArraySetting(GroupSetting[] sections, string name) -{ - return findSetting!ArraySetting(sections, Setting.Type.array, name); + return result; } -ScalarSetting findScalarSetting(GroupSetting[] sections, string name) +string findScalarSetting(GroupSetting[] sections, string name) { - return findSetting!ScalarSetting(sections, Setting.Type.scalar, name); + string result = null; + foreach (section; sections) + { + foreach (c; section.children) + { + if (c.type == Setting.Type.scalar && c.name == name) + result = (cast(ScalarSetting) c).val; + } + } + return result; } string replace(string str, string pattern, string replacement) @@ -133,12 +142,10 @@ private: Array!(const(char)*) _libDirs; const(char)* rpathcstr; - static bool sectionMatches(const(char)* section, const(char)* triple); + static bool sectionMatches(const(char)* section, const(char)* triple) nothrow; bool readConfig(const(char)* cfPath, const(char)* triple, const(char)* binDir) { - switches.setDim(0); - postSwitches.setDim(0); const cfgPaths = CfgPaths(cfPath, binDir); try @@ -147,7 +154,7 @@ private: foreach (s; parseConfigFile(cfPath)) { if (s.type == Setting.Type.group && - (s.name == "default" || sectionMatches((s.name ~ '\0').ptr, triple))) + (s.name == "default" || s.name.toCStringThen!(name => sectionMatches(name.ptr, triple)))) { sections ~= cast(GroupSetting) s; } @@ -155,29 +162,23 @@ private: if (sections.length == 0) { - const dTriple = triple[0 .. strlen(triple)]; - const dCfPath = cfPath[0 .. strlen(cfPath)]; - throw new Exception("No matching section for triple '" ~ cast(string) dTriple - ~ "' in " ~ cast(string) dCfPath); + throw new Exception("No matching section for triple '" ~ cast(string) triple.toDString + ~ "'"); } - auto switches = findArraySetting(sections, "switches"); - auto postSwitches = findArraySetting(sections, "post-switches"); - if (!switches && !postSwitches) - { - const dCfPath = cfPath[0 .. strlen(cfPath)]; - throw new Exception("Could not look up switches in " ~ cast(string) dCfPath); - } + const switches = findArraySetting(sections, "switches"); + const postSwitches = findArraySetting(sections, "post-switches"); + if (switches.length + postSwitches.length == 0) + throw new Exception("Could not look up switches"); - void applyArray(ref Array!(const(char)*) output, ArraySetting input) + void applyArray(ref Array!(const(char)*) output, const(string)[] input) { - if (!input) - return; + output.setDim(0); - output.reserve(input.vals.length); - foreach (sw; input.vals) + output.reserve(input.length); + foreach (sw; input) { - const finalSwitch = sw.replacePlaceholders(cfgPaths) ~ '\0'; + const finalSwitch = sw.replacePlaceholders(cfgPaths).toCString; output.push(finalSwitch.ptr); } } @@ -185,17 +186,17 @@ private: applyArray(this.switches, switches); applyArray(this.postSwitches, postSwitches); - auto libDirs = findArraySetting(sections, "lib-dirs"); + const libDirs = findArraySetting(sections, "lib-dirs"); applyArray(_libDirs, libDirs); - if (auto rpath = findScalarSetting(sections, "rpath")) - this.rpathcstr = (rpath.val.replacePlaceholders(cfgPaths) ~ '\0').ptr; + const rpath = findScalarSetting(sections, "rpath"); + this.rpathcstr = rpath.length == 0 ? null : rpath.replacePlaceholders(cfgPaths).toCString.ptr; return true; } catch (Exception ex) { - fprintf(stderr, "Error: %.*s\n", cast(int) ex.msg.length, ex.msg.ptr); + fprintf(stderr, "Error while reading config file: %s\n%.*s\n", cfPath, cast(int) ex.msg.length, ex.msg.ptr); return false; } } diff --git a/tests/driver/config_append_assign.d b/tests/driver/config_append_assign.d new file mode 100644 index 00000000000..6ff31defa47 --- /dev/null +++ b/tests/driver/config_append_assign.d @@ -0,0 +1,11 @@ +// RUN: %ldc -o- -v -conf=%S/inputs/appending_assign.conf %s 2>&1 + +module object; + +version(Section1_1) +static assert(false); +version(Section1_2) {} +else static assert(false); + +version(Section2) {} +else static assert(false); diff --git a/tests/driver/config_diag.d b/tests/driver/config_diag.d index 31cebd3cd7a..eae17e18c0a 100644 --- a/tests/driver/config_diag.d +++ b/tests/driver/config_diag.d @@ -1,10 +1,13 @@ -// RUN: not %ldc -conf=%S/inputs/noswitches.conf %s 2>&1 | FileCheck %s --check-prefix=NOSWITCHES -// NOSWITCHES: Could not look up switches in {{.*}}noswitches.conf +// RUN: %ldc -o- -conf=%S/inputs/noswitches.conf %s 2>&1 | FileCheck %s --check-prefix=NOSWITCHES +// NOSWITCHES: Error while reading config file: {{.*}}noswitches.conf +// NOSWITCHES-NEXT: Could not look up switches -// RUN: not %ldc -conf=%S/inputs/section_aaa.conf %s 2>&1 | FileCheck %s --check-prefix=NO_SEC -// NO_SEC: No matching section for triple '{{.*}}' in {{.*}}section_aaa.conf +// RUN: %ldc -o- -conf=%S/inputs/section_aaa.conf %s 2>&1 | FileCheck %s --check-prefix=NO_SEC +// NO_SEC: Error while reading config file: {{.*}}section_aaa.conf +// NO_SEC-NEXT: No matching section for triple '{{.*}}' +// RUN: %ldc -o- -conf=%S/inputs/invalid_append.conf %s 2>&1 | FileCheck %s --check-prefix=APP +// APP: Error while reading config file: {{.*}}invalid_append.conf +// APP-NEXT: line 3: '~=' is not supported with scalar values -void foo() -{ -} +module object; diff --git a/tests/driver/inputs/appending_assign.conf b/tests/driver/inputs/appending_assign.conf new file mode 100644 index 00000000000..67a857761bd --- /dev/null +++ b/tests/driver/inputs/appending_assign.conf @@ -0,0 +1,8 @@ +default: { + switches = [ "-d-version=Section1_1" ] + switches = [ "-d-version=Section1_2" ] +} + +".?": { + switches ~= [ "-d-version=Section2" ] +} diff --git a/tests/driver/inputs/invalid_append.conf b/tests/driver/inputs/invalid_append.conf new file mode 100644 index 00000000000..bca0b67c38f --- /dev/null +++ b/tests/driver/inputs/invalid_append.conf @@ -0,0 +1,10 @@ +default: +{ + rpath ~= "/path"; +} + +default: +{ + switches = []; + post-switches = []; +}