@@ -66,18 +66,25 @@ class ScalarSetting : Setting
66
66
67
67
class ArraySetting : Setting
68
68
{
69
- this (string name, string [] vals)
69
+ this (string name, string [] vals, bool isAppending )
70
70
{
71
71
super (name, Type.array);
72
72
_vals = vals;
73
+ _isAppending = isAppending;
73
74
}
74
75
75
76
@property const (string )[] vals() const
76
77
{
77
78
return _vals;
78
79
}
79
80
81
+ @property bool isAppending() const
82
+ {
83
+ return _isAppending;
84
+ }
85
+
80
86
private string [] _vals;
87
+ private bool _isAppending;
81
88
}
82
89
83
90
class GroupSetting : Setting
@@ -133,7 +140,7 @@ EBNF grammar.
133
140
It is a subset of the libconfig grammar (http://www.hyperrealm.com/libconfig).
134
141
135
142
config = { ows , setting } , ows ;
136
- setting = (name | string) , (":" | "=") , value , [";" | ","] ;
143
+ setting = (name | string) , (":" | "=" | "~=" ) , value , [";" | ","] ;
137
144
name = alpha , { alpha | digit | "_" | "-" } ;
138
145
value = string | array | group ;
139
146
array = "[" , ows ,
@@ -172,6 +179,7 @@ enum Token
172
179
{
173
180
name,
174
181
assign, // ':' or '='
182
+ appendAssign, // '~='
175
183
str,
176
184
lbrace, // '{'
177
185
rbrace, // '}'
@@ -187,17 +195,18 @@ string humanReadableToken(in Token tok)
187
195
{
188
196
final switch (tok)
189
197
{
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"` ;
201
210
}
202
211
}
203
212
@@ -226,11 +235,14 @@ struct Parser
226
235
227
236
void error (in string msg)
228
237
{
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);
234
246
}
235
247
236
248
char getChar ()
@@ -275,6 +287,19 @@ struct Parser
275
287
return getTok (outStr);
276
288
}
277
289
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
+
278
303
if (isalpha(lastChar))
279
304
{
280
305
string name;
@@ -410,17 +435,6 @@ struct Parser
410
435
" . Got " ~ humanReadableToken(tok) ~ s ~ " instead." );
411
436
}
412
437
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
-
424
438
Setting[] parseConfig ()
425
439
{
426
440
Setting[] res;
@@ -450,11 +464,29 @@ struct Parser
450
464
assert (false );
451
465
}
452
466
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;
454
480
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
+ }
456
489
457
- string s;
458
490
t = getTok(s);
459
491
if (t != Token .semicolon && t != Token .comma)
460
492
{
@@ -464,8 +496,10 @@ struct Parser
464
496
return res;
465
497
}
466
498
467
- Setting parseValue (string name)
499
+ Setting parseValue (string name, Token tAssign = Token .assign )
468
500
{
501
+ assert (tAssign == Token .assign || tAssign == Token .appendAssign);
502
+
469
503
string s;
470
504
auto t = getTok(s);
471
505
if (t == Token .str)
@@ -474,6 +508,7 @@ struct Parser
474
508
}
475
509
else if (t == Token .lbracket)
476
510
{
511
+ const isAppending = tAssign == Token .appendAssign;
477
512
string [] arrVal;
478
513
while (1 )
479
514
{
@@ -485,7 +520,7 @@ struct Parser
485
520
arrVal ~= s;
486
521
break ;
487
522
case Token .rbracket:
488
- return new ArraySetting(name, arrVal);
523
+ return new ArraySetting(name, arrVal, isAppending );
489
524
default :
490
525
unexpectedTokenError(t, Token .str, s);
491
526
assert (false );
@@ -498,7 +533,7 @@ struct Parser
498
533
case Token .comma:
499
534
break ;
500
535
case Token .rbracket:
501
- return new ArraySetting(name, arrVal);
536
+ return new ArraySetting(name, arrVal, isAppending );
502
537
default :
503
538
unexpectedTokenError(t, Token .comma, s);
504
539
assert (false );
@@ -578,6 +613,8 @@ group-1_2: {};
578
613
scalar = "abc";
579
614
// comment
580
615
Array_1-2 = [ "a" ];
616
+
617
+ AppArray ~= [ "x" ]; // appending array
581
618
};
582
619
` ;
583
620
@@ -591,7 +628,7 @@ group-1_2: {};
591
628
assert (settings[1 ].name == " 86(_64)?-.*linux\\ .?" );
592
629
assert (settings[1 ].type == Setting.Type.group);
593
630
auto group2 = cast (GroupSetting) settings[1 ];
594
- assert (group2.children.length == 2 );
631
+ assert (group2.children.length == 3 );
595
632
596
633
assert (group2.children[0 ].name == " scalar" );
597
634
assert (group2.children[0 ].type == Setting.Type.scalar);
@@ -600,4 +637,10 @@ group-1_2: {};
600
637
assert (group2.children[1 ].name == " Array_1-2" );
601
638
assert (group2.children[1 ].type == Setting.Type.array);
602
639
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 );
603
646
}
0 commit comments