@@ -70,18 +70,25 @@ class ScalarSetting : Setting
7070
7171class ArraySetting : Setting
7272{
73- this (string name, string [] vals)
73+ this (string name, string [] vals, bool isAppending )
7474 {
7575 super (name, Type.array);
7676 _vals = vals;
77+ _isAppending = isAppending;
7778 }
7879
7980 @property const (string )[] vals() const
8081 {
8182 return _vals;
8283 }
8384
85+ @property bool isAppending() const
86+ {
87+ return _isAppending;
88+ }
89+
8490 private string [] _vals;
91+ private bool _isAppending;
8592}
8693
8794class GroupSetting : Setting
@@ -125,7 +132,7 @@ EBNF grammar.
125132It is a subset of the libconfig grammar (http://www.hyperrealm.com/libconfig).
126133
127134config = { ows , setting } , ows ;
128- setting = (name | string) , (":" | "=") , value , [";" | ","] ;
135+ setting = (name | string) , (":" | "=" | "~=" ) , value , [";" | ","] ;
129136name = alpha , { alpha | digit | "_" | "-" } ;
130137value = string | array | group ;
131138array = "[" , ows ,
@@ -164,6 +171,7 @@ enum Token
164171{
165172 name,
166173 assign, // ':' or '='
174+ appendAssign, // '~='
167175 str,
168176 lbrace, // '{'
169177 rbrace, // '}'
@@ -179,17 +187,18 @@ string humanReadableToken(in Token tok)
179187{
180188 final switch (tok)
181189 {
182- case Token .name: return ` "name"` ;
183- case Token .assign: return ` ':' or '='` ;
184- case Token .str: return ` "string"` ;
185- case Token .lbrace: return ` '{'` ;
186- case Token .rbrace: return ` '}'` ;
187- case Token .lbracket: return ` '['` ;
188- case Token .rbracket: return ` ']'` ;
189- case Token .semicolon: return ` ';'` ;
190- case Token .comma: return ` ','` ;
191- case Token .unknown: return ` "unknown token"` ;
192- case Token .eof: return ` "end of file"` ;
190+ case Token .name: return ` "name"` ;
191+ case Token .assign: return ` ':' or '='` ;
192+ case Token .appendAssign: return ` '~='` ;
193+ case Token .str: return ` "string"` ;
194+ case Token .lbrace: return ` '{'` ;
195+ case Token .rbrace: return ` '}'` ;
196+ case Token .lbracket: return ` '['` ;
197+ case Token .rbracket: return ` ']'` ;
198+ case Token .semicolon: return ` ';'` ;
199+ case Token .comma: return ` ','` ;
200+ case Token .unknown: return ` "unknown token"` ;
201+ case Token .eof: return ` "end of file"` ;
193202 }
194203}
195204
@@ -218,11 +227,14 @@ struct Parser
218227
219228 void error (in string msg)
220229 {
221- enum fmt = " Error while reading config file: %.*s\n line %d: %.*s" ;
222- char [1024 ] buf;
223- auto len = snprintf(buf.ptr, buf.length, fmt, cast (int ) filename.length,
224- filename.ptr, lineNum, cast (int ) msg.length, msg.ptr);
225- throw new Exception (buf[0 .. len].idup);
230+ error(msg, lineNum);
231+ }
232+
233+ void error (in string msg, int lineNum)
234+ {
235+ char [20 ] buf = void ;
236+ auto len = snprintf(buf.ptr, buf.length, " line %d: " , lineNum);
237+ throw new Exception ((cast (string ) buf[0 .. len]) ~ msg);
226238 }
227239
228240 char getChar ()
@@ -267,6 +279,19 @@ struct Parser
267279 return getTok (outStr);
268280 }
269281
282+ if (lastChar == ' ~' )
283+ {
284+ lastChar = getChar();
285+ if (lastChar != ' =' )
286+ {
287+ outStr = " ~" ;
288+ return Token .unknown;
289+ }
290+
291+ lastChar = getChar();
292+ return Token .appendAssign;
293+ }
294+
270295 if (isalpha(lastChar))
271296 {
272297 string name;
@@ -402,17 +427,6 @@ struct Parser
402427 " . Got " ~ humanReadableToken(tok) ~ s ~ " instead." );
403428 }
404429
405- string accept (in Token expected)
406- {
407- string s;
408- immutable tok = getTok(s);
409- if (tok != expected)
410- {
411- unexpectedTokenError(tok, expected, s);
412- }
413- return s;
414- }
415-
416430 Setting[] parseConfig ()
417431 {
418432 Setting[] res;
@@ -442,11 +456,29 @@ struct Parser
442456 assert (false );
443457 }
444458
445- accept(Token .assign);
459+ string s;
460+ t = getTok(s);
461+ if (t != Token .assign && t != Token .appendAssign)
462+ {
463+ auto msg = " Expected either"
464+ ~ " token " ~ humanReadableToken(Token .assign)
465+ ~ " or token " ~ humanReadableToken(Token .appendAssign)
466+ ~ " but got: " ~ humanReadableToken(t)
467+ ~ ' ' ~ (s.length ? ' (' ~ s ~ ' )' : s);
468+ error(msg);
469+ }
470+ // This is off by +1 if `t` is followed by \n
471+ const assignLineNum = lineNum;
446472
447- Setting res = parseValue(name);
473+ Setting res = parseValue(name, t);
474+ if (t == Token .appendAssign)
475+ {
476+ if (res.type == Setting.Type.scalar)
477+ error(humanReadableToken(t) ~ " is not supported with scalar values" , assignLineNum);
478+ if (res.type == Setting.Type.group)
479+ error(humanReadableToken(t) ~ " is not supported with groups" , assignLineNum);
480+ }
448481
449- string s;
450482 t = getTok(s);
451483 if (t != Token .semicolon && t != Token .comma)
452484 {
@@ -456,8 +488,10 @@ struct Parser
456488 return res;
457489 }
458490
459- Setting parseValue (string name)
491+ Setting parseValue (string name, Token tAssign = Token .assign )
460492 {
493+ assert (tAssign == Token .assign || tAssign == Token .appendAssign);
494+
461495 string s;
462496 auto t = getTok(s);
463497 if (t == Token .str)
@@ -466,6 +500,7 @@ struct Parser
466500 }
467501 else if (t == Token .lbracket)
468502 {
503+ const isAppending = tAssign == Token .appendAssign;
469504 string [] arrVal;
470505 while (1 )
471506 {
@@ -477,7 +512,7 @@ struct Parser
477512 arrVal ~= s;
478513 break ;
479514 case Token .rbracket:
480- return new ArraySetting(name, arrVal);
515+ return new ArraySetting(name, arrVal, isAppending );
481516 default :
482517 unexpectedTokenError(t, Token .str, s);
483518 assert (false );
@@ -490,7 +525,7 @@ struct Parser
490525 case Token .comma:
491526 break ;
492527 case Token .rbracket:
493- return new ArraySetting(name, arrVal);
528+ return new ArraySetting(name, arrVal, isAppending );
494529 default :
495530 unexpectedTokenError(t, Token .comma, s);
496531 assert (false );
@@ -570,6 +605,8 @@ group-1_2: {};
570605 scalar = "abc";
571606 // comment
572607 Array_1-2 = [ "a" ];
608+
609+ AppArray ~= [ "x" ]; // appending array
573610};
574611` ;
575612
@@ -583,7 +620,7 @@ group-1_2: {};
583620 assert (settings[1 ].name == " 86(_64)?-.*linux\\ .?" );
584621 assert (settings[1 ].type == Setting.Type.group);
585622 auto group2 = cast (GroupSetting) settings[1 ];
586- assert (group2.children.length == 2 );
623+ assert (group2.children.length == 3 );
587624
588625 assert (group2.children[0 ].name == " scalar" );
589626 assert (group2.children[0 ].type == Setting.Type.scalar);
@@ -592,4 +629,10 @@ group-1_2: {};
592629 assert (group2.children[1 ].name == " Array_1-2" );
593630 assert (group2.children[1 ].type == Setting.Type.array);
594631 assert ((cast (ArraySetting) group2.children[1 ]).vals == [ " a" ]);
632+ assert ((cast (ArraySetting) group2.children[1 ]).isAppending == false );
633+
634+ assert (group2.children[2 ].name == " AppArray" );
635+ assert (group2.children[2 ].type == Setting.Type.array);
636+ assert ((cast (ArraySetting) group2.children[2 ]).vals == [ " x" ]);
637+ assert ((cast (ArraySetting) group2.children[2 ]).isAppending == true );
595638}
0 commit comments