@@ -66,18 +66,25 @@ class ScalarSetting : Setting
6666
6767class ArraySetting : Setting
6868{
69- this (string name, string [] vals)
69+ this (string name, string [] vals, bool isAppending )
7070 {
7171 super (name, Type.array);
7272 _vals = vals;
73+ _isAppending = isAppending;
7374 }
7475
7576 @property const (string )[] vals() const
7677 {
7778 return _vals;
7879 }
7980
81+ @property bool isAppending() const
82+ {
83+ return _isAppending;
84+ }
85+
8086 private string [] _vals;
87+ private bool _isAppending;
8188}
8289
8390class GroupSetting : Setting
@@ -133,7 +140,7 @@ EBNF grammar.
133140It is a subset of the libconfig grammar (http://www.hyperrealm.com/libconfig).
134141
135142config = { ows , setting } , ows ;
136- setting = (name | string) , (":" | "=") , value , [";" | ","] ;
143+ setting = (name | string) , (":" | "=" | "~=" ) , value , [";" | ","] ;
137144name = alpha , { alpha | digit | "_" | "-" } ;
138145value = string | array | group ;
139146array = "[" , ows ,
@@ -172,6 +179,7 @@ enum Token
172179{
173180 name,
174181 assign, // ':' or '='
182+ appendAssign, // '~='
175183 str,
176184 lbrace, // '{'
177185 rbrace, // '}'
@@ -187,17 +195,18 @@ string humanReadableToken(in Token tok)
187195{
188196 final switch (tok)
189197 {
190- case Token .name: return ` "name"` ;
191- case Token .assign: return ` ':' or '='` ;
192- case Token .str: return ` "string"` ;
193- case Token .lbrace: return ` '{'` ;
194- case Token .rbrace: return ` '}'` ;
195- case Token .lbracket: return ` '['` ;
196- case Token .rbracket: return ` ']'` ;
197- case Token .semicolon: return ` ';'` ;
198- case Token .comma: return ` ','` ;
199- case Token .unknown: return ` "unknown token"` ;
200- case Token .eof: return ` "end of file"` ;
198+ case Token .name: return ` "name"` ;
199+ case Token .assign: return ` ':' or '='` ;
200+ case Token .appendAssign: return ` '~='` ;
201+ case Token .str: return ` "string"` ;
202+ case Token .lbrace: return ` '{'` ;
203+ case Token .rbrace: return ` '}'` ;
204+ case Token .lbracket: return ` '['` ;
205+ case Token .rbracket: return ` ']'` ;
206+ case Token .semicolon: return ` ';'` ;
207+ case Token .comma: return ` ','` ;
208+ case Token .unknown: return ` "unknown token"` ;
209+ case Token .eof: return ` "end of file"` ;
201210 }
202211}
203212
@@ -226,11 +235,14 @@ struct Parser
226235
227236 void error (in string msg)
228237 {
229- enum fmt = " Error while reading config file: %.*s\n line %d: %.*s" ;
230- char [1024 ] buf;
231- auto len = snprintf(buf.ptr, buf.length, fmt, cast (int ) filename.length,
232- filename.ptr, lineNum, cast (int ) msg.length, msg.ptr);
233- throw new Exception (buf[0 .. len].idup);
238+ error(msg, lineNum);
239+ }
240+
241+ void error (in string msg, int lineNum)
242+ {
243+ char [20 ] buf = void ;
244+ auto len = snprintf(buf.ptr, buf.length, " line %d: " , lineNum);
245+ throw new Exception ((cast (string ) buf[0 .. len]) ~ msg);
234246 }
235247
236248 char getChar ()
@@ -275,6 +287,19 @@ struct Parser
275287 return getTok (outStr);
276288 }
277289
290+ if (lastChar == ' ~' )
291+ {
292+ lastChar = getChar();
293+ if (lastChar != ' =' )
294+ {
295+ outStr = " ~" ;
296+ return Token .unknown;
297+ }
298+
299+ lastChar = getChar();
300+ return Token .appendAssign;
301+ }
302+
278303 if (isalpha(lastChar))
279304 {
280305 string name;
@@ -410,17 +435,6 @@ struct Parser
410435 " . Got " ~ humanReadableToken(tok) ~ s ~ " instead." );
411436 }
412437
413- string accept (in Token expected)
414- {
415- string s;
416- immutable tok = getTok(s);
417- if (tok != expected)
418- {
419- unexpectedTokenError(tok, expected, s);
420- }
421- return s;
422- }
423-
424438 Setting[] parseConfig ()
425439 {
426440 Setting[] res;
@@ -450,11 +464,29 @@ struct Parser
450464 assert (false );
451465 }
452466
453- accept(Token .assign);
467+ string s;
468+ t = getTok(s);
469+ if (t != Token .assign && t != Token .appendAssign)
470+ {
471+ auto msg = " Expected either"
472+ ~ " token " ~ humanReadableToken(Token .assign)
473+ ~ " or token " ~ humanReadableToken(Token .appendAssign)
474+ ~ " but got: " ~ humanReadableToken(t)
475+ ~ ' ' ~ (s.length ? ' (' ~ s ~ ' )' : s);
476+ error(msg);
477+ }
478+ // This is off by +1 if `t` is followed by \n
479+ const assignLineNum = lineNum;
454480
455- Setting res = parseValue(name);
481+ Setting res = parseValue(name, t);
482+ if (t == Token .appendAssign)
483+ {
484+ if (res.type == Setting.Type.scalar)
485+ error(humanReadableToken(t) ~ " is not supported with scalar values" , assignLineNum);
486+ if (res.type == Setting.Type.group)
487+ error(humanReadableToken(t) ~ " is not supported with groups" , assignLineNum);
488+ }
456489
457- string s;
458490 t = getTok(s);
459491 if (t != Token .semicolon && t != Token .comma)
460492 {
@@ -464,8 +496,10 @@ struct Parser
464496 return res;
465497 }
466498
467- Setting parseValue (string name)
499+ Setting parseValue (string name, Token tAssign = Token .assign )
468500 {
501+ assert (tAssign == Token .assign || tAssign == Token .appendAssign);
502+
469503 string s;
470504 auto t = getTok(s);
471505 if (t == Token .str)
@@ -474,6 +508,7 @@ struct Parser
474508 }
475509 else if (t == Token .lbracket)
476510 {
511+ const isAppending = tAssign == Token .appendAssign;
477512 string [] arrVal;
478513 while (1 )
479514 {
@@ -485,7 +520,7 @@ struct Parser
485520 arrVal ~= s;
486521 break ;
487522 case Token .rbracket:
488- return new ArraySetting(name, arrVal);
523+ return new ArraySetting(name, arrVal, isAppending );
489524 default :
490525 unexpectedTokenError(t, Token .str, s);
491526 assert (false );
@@ -498,7 +533,7 @@ struct Parser
498533 case Token .comma:
499534 break ;
500535 case Token .rbracket:
501- return new ArraySetting(name, arrVal);
536+ return new ArraySetting(name, arrVal, isAppending );
502537 default :
503538 unexpectedTokenError(t, Token .comma, s);
504539 assert (false );
@@ -578,6 +613,8 @@ group-1_2: {};
578613 scalar = "abc";
579614 // comment
580615 Array_1-2 = [ "a" ];
616+
617+ AppArray ~= [ "x" ]; // appending array
581618};
582619` ;
583620
@@ -591,7 +628,7 @@ group-1_2: {};
591628 assert (settings[1 ].name == " 86(_64)?-.*linux\\ .?" );
592629 assert (settings[1 ].type == Setting.Type.group);
593630 auto group2 = cast (GroupSetting) settings[1 ];
594- assert (group2.children.length == 2 );
631+ assert (group2.children.length == 3 );
595632
596633 assert (group2.children[0 ].name == " scalar" );
597634 assert (group2.children[0 ].type == Setting.Type.scalar);
@@ -600,4 +637,10 @@ group-1_2: {};
600637 assert (group2.children[1 ].name == " Array_1-2" );
601638 assert (group2.children[1 ].type == Setting.Type.array);
602639 assert ((cast (ArraySetting) group2.children[1 ]).vals == [ " a" ]);
640+ assert ((cast (ArraySetting) group2.children[1 ]).isAppending == false );
641+
642+ assert (group2.children[2 ].name == " AppArray" );
643+ assert (group2.children[2 ].type == Setting.Type.array);
644+ assert ((cast (ArraySetting) group2.children[2 ]).vals == [ " x" ]);
645+ assert ((cast (ArraySetting) group2.children[2 ]).isAppending == true );
603646}
0 commit comments