@@ -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,10 +235,15 @@ 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" ;
238
+ error(msg, lineNum);
239
+ }
240
+
241
+ void error (in string msg, int lineNum)
242
+ {
243
+ enum fmt = " line %d: %.*s" ;
230
244
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);
245
+ auto len = snprintf(buf.ptr, buf.length, fmt,
246
+ lineNum, cast (int ) msg.length, msg.ptr);
233
247
throw new Exception (buf[0 .. len].idup);
234
248
}
235
249
@@ -275,6 +289,19 @@ struct Parser
275
289
return getTok (outStr);
276
290
}
277
291
292
+ if (lastChar == ' ~' )
293
+ {
294
+ lastChar = getChar();
295
+ if (lastChar != ' =' )
296
+ {
297
+ outStr = " ~" ;
298
+ return Token .unknown;
299
+ }
300
+
301
+ lastChar = getChar();
302
+ return Token .appendAssign;
303
+ }
304
+
278
305
if (isalpha(lastChar))
279
306
{
280
307
string name;
@@ -410,17 +437,6 @@ struct Parser
410
437
" . Got " ~ humanReadableToken(tok) ~ s ~ " instead." );
411
438
}
412
439
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
440
Setting[] parseConfig ()
425
441
{
426
442
Setting[] res;
@@ -450,11 +466,29 @@ struct Parser
450
466
assert (false );
451
467
}
452
468
453
- accept(Token .assign);
469
+ string s;
470
+ t = getTok(s);
471
+ if (t != Token .assign && t != Token .appendAssign)
472
+ {
473
+ auto msg = " Expected either"
474
+ ~ " token " ~ humanReadableToken(Token .assign)
475
+ ~ " or token " ~ humanReadableToken(Token .appendAssign)
476
+ ~ " but got: " ~ humanReadableToken(t)
477
+ ~ ' ' ~ (s.length ? ' (' ~ s ~ ' )' : s);
478
+ error(msg);
479
+ }
480
+ // This is off by +1 if `t` is followed by \n
481
+ const assignLineNum = lineNum;
454
482
455
- Setting res = parseValue(name);
483
+ Setting res = parseValue(name, t);
484
+ if (t == Token .appendAssign)
485
+ {
486
+ if (res.type == Setting.Type.scalar)
487
+ error(humanReadableToken(t) ~ " is not supported with scalar values" , assignLineNum);
488
+ if (res.type == Setting.Type.group)
489
+ error(humanReadableToken(t) ~ " is not supported with groups" , assignLineNum);
490
+ }
456
491
457
- string s;
458
492
t = getTok(s);
459
493
if (t != Token .semicolon && t != Token .comma)
460
494
{
@@ -464,8 +498,10 @@ struct Parser
464
498
return res;
465
499
}
466
500
467
- Setting parseValue (string name)
501
+ Setting parseValue (string name, Token tAssign = Token .assign )
468
502
{
503
+ assert (tAssign == Token .assign || tAssign == Token .appendAssign);
504
+
469
505
string s;
470
506
auto t = getTok(s);
471
507
if (t == Token .str)
@@ -474,6 +510,7 @@ struct Parser
474
510
}
475
511
else if (t == Token .lbracket)
476
512
{
513
+ const isAppending = tAssign == Token .appendAssign;
477
514
string [] arrVal;
478
515
while (1 )
479
516
{
@@ -485,7 +522,7 @@ struct Parser
485
522
arrVal ~= s;
486
523
break ;
487
524
case Token .rbracket:
488
- return new ArraySetting(name, arrVal);
525
+ return new ArraySetting(name, arrVal, isAppending );
489
526
default :
490
527
unexpectedTokenError(t, Token .str, s);
491
528
assert (false );
@@ -498,7 +535,7 @@ struct Parser
498
535
case Token .comma:
499
536
break ;
500
537
case Token .rbracket:
501
- return new ArraySetting(name, arrVal);
538
+ return new ArraySetting(name, arrVal, isAppending );
502
539
default :
503
540
unexpectedTokenError(t, Token .comma, s);
504
541
assert (false );
@@ -578,6 +615,8 @@ group-1_2: {};
578
615
scalar = "abc";
579
616
// comment
580
617
Array_1-2 = [ "a" ];
618
+
619
+ AppArray ~= [ "x" ]; // appending array
581
620
};
582
621
` ;
583
622
@@ -591,7 +630,7 @@ group-1_2: {};
591
630
assert (settings[1 ].name == " 86(_64)?-.*linux\\ .?" );
592
631
assert (settings[1 ].type == Setting.Type.group);
593
632
auto group2 = cast (GroupSetting) settings[1 ];
594
- assert (group2.children.length == 2 );
633
+ assert (group2.children.length == 3 );
595
634
596
635
assert (group2.children[0 ].name == " scalar" );
597
636
assert (group2.children[0 ].type == Setting.Type.scalar);
@@ -600,4 +639,10 @@ group-1_2: {};
600
639
assert (group2.children[1 ].name == " Array_1-2" );
601
640
assert (group2.children[1 ].type == Setting.Type.array);
602
641
assert ((cast (ArraySetting) group2.children[1 ]).vals == [ " a" ]);
642
+ assert ((cast (ArraySetting) group2.children[1 ]).isAppending == false );
643
+
644
+ assert (group2.children[2 ].name == " AppArray" );
645
+ assert (group2.children[2 ].type == Setting.Type.array);
646
+ assert ((cast (ArraySetting) group2.children[2 ]).vals == [ " x" ]);
647
+ assert ((cast (ArraySetting) group2.children[2 ]).isAppending == true );
603
648
}
0 commit comments