diff --git a/driver/config.d b/driver/config.d index 46990a49a93..a829c6d13aa 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,10 +235,15 @@ struct Parser void error(in string msg) { - enum fmt = "Error while reading config file: %.*s\nline %d: %.*s"; + error(msg, lineNum); + } + + void error(in string msg, int lineNum) + { + enum fmt = "line %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); + auto len = snprintf(buf.ptr, buf.length, fmt, + lineNum, cast(int) msg.length, msg.ptr); throw new Exception(buf[0 .. len].idup); } @@ -275,6 +289,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 +437,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 +466,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 +498,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 +510,7 @@ struct Parser } else if (t == Token.lbracket) { + const isAppending = tAssign == Token.appendAssign; string[] arrVal; while (1) { @@ -485,7 +522,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 +535,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 +615,8 @@ group-1_2: {}; scalar = "abc"; // comment Array_1-2 = [ "a" ]; + + AppArray ~= [ "x" ]; // appending array }; `; @@ -591,7 +630,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 +639,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..f77b17eb04f 100644 --- a/driver/configfile.d +++ b/driver/configfile.d @@ -30,28 +30,42 @@ string normalizeSlashes(const(char)* binDir) return cast(string)res; // assumeUnique } -T findSetting(T)(GroupSetting[] sections, Setting.Type type, string name) +T findSetting(T, alias reducer)(GroupSetting[] sections, Setting.Type type, string name) { - // lexically later sections dominate earlier ones - foreach_reverse (section; sections) + T result = null; + foreach (section; sections) { foreach (c; section.children) { if (c.type == type && c.name == name) - return cast(T) c; + { + if (result !is null) + result = reducer(result, cast(T) c); + else + result = cast(T) c; + } } } - return null; + return result; } ArraySetting findArraySetting(GroupSetting[] sections, string name) { - return findSetting!ArraySetting(sections, Setting.Type.array, name); + auto merge(ArraySetting a, ArraySetting b) + { + string[] newVals; + if (b.isAppending) + newVals = [] ~ a.vals ~ b.vals; + else + newVals = [] ~ b.vals; + return new ArraySetting(a.name, newVals, a.isAppending && b.isAppending); + } + return findSetting!(ArraySetting, merge)(sections, Setting.Type.array, name); } ScalarSetting findScalarSetting(GroupSetting[] sections, string name) { - return findSetting!ScalarSetting(sections, Setting.Type.scalar, name); + return findSetting!(ScalarSetting, (o, n) => n)(sections, Setting.Type.scalar, name); } string replace(string str, string pattern, string replacement) @@ -137,8 +151,6 @@ private: bool readConfig(const(char)* cfPath, const(char)* triple, const(char)* binDir) { - switches.setDim(0); - postSwitches.setDim(0); const cfgPaths = CfgPaths(cfPath, binDir); try @@ -156,24 +168,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); + ~ "'"); } 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); - } + throw new Exception("Could not look up switches"); void applyArray(ref Array!(const(char)*) output, ArraySetting input) { if (!input) return; + if (!input.isAppending) + output.setDim(0); + output.reserve(input.vals.length); foreach (sw; input.vals) { @@ -195,7 +206,7 @@ private: } 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 = []; +}