cfg-config 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/CFG/config.rb ADDED
@@ -0,0 +1,2090 @@
1
+ require 'date'
2
+ require 'set'
3
+ require 'stringio'
4
+
5
+ require 'CFG/version'
6
+
7
+ module CFG
8
+ module Utils
9
+ def white_space?(chr)
10
+ chr =~ /[[:space:]]/
11
+ end
12
+
13
+ def letter?(chr)
14
+ chr =~ /[[:alpha:]]/
15
+ end
16
+
17
+ def digit?(chr)
18
+ chr =~ /[[:digit:]]/
19
+ end
20
+
21
+ def alnum?(chr)
22
+ chr =~ /[[:alnum:]]/
23
+ end
24
+
25
+ def hexdigit?(chr)
26
+ chr =~ /[[:xdigit:]]/
27
+ end
28
+
29
+ ISO_DATETIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})
30
+ (([ T])(((\d{2}):(\d{2}):(\d{2}))(\.\d{1,6})?
31
+ (([+-])(\d{2}):(\d{2})(:(\d{2})(\.\d{1,6})?)?)?))?$/x.freeze
32
+ ENV_VALUE_PATTERN = /^\$(\w+)(\|(.*))?$/.freeze
33
+ COLON_OBJECT_PATTERN = /^(\p{L}\w*(\/\p{L}\w*)*:::)?(\p{Lu}\w*(::\p{Lu}\w*)*)
34
+ (\.([\p{L}_]\w*(\.[\p{L}_]\w*)*))?$/xu.freeze
35
+ INTERPOLATION_PATTERN = /(\$\{([^}]+)\})/.freeze
36
+
37
+ def default_string_converter(str, cfg)
38
+ result = str
39
+ m = ISO_DATETIME_PATTERN.match str
40
+
41
+ if !m.nil?
42
+ year = m[1].to_i
43
+ month = m[2].to_i
44
+ day = m[3].to_i
45
+ has_time = !m[5].nil?
46
+ if !has_time
47
+ result = Date.new year, month, day
48
+ else
49
+ hour = m[8].to_i
50
+ minute = m[9].to_i
51
+ second = m[10].to_i
52
+ fracseconds = if m[11].nil?
53
+ 0
54
+ else
55
+ m[11].to_f
56
+ end
57
+ offset = if m[13].nil?
58
+ 0
59
+ else
60
+ sign = m[13] == '-' ? -1 : 1
61
+ ohour = m[14].to_i
62
+ ominute = m[15].to_i
63
+ osecond = m[17] ? m[17].to_i : 0
64
+ (osecond + ominute * 60 +
65
+ ohour * 3600) * sign / 86_400.0
66
+ end
67
+ result = DateTime.new(year, month, day, hour, minute,
68
+ fracseconds + second, offset)
69
+ end
70
+ else
71
+ m = ENV_VALUE_PATTERN.match str
72
+ if !m.nil?
73
+ var_name = m[1]
74
+ has_pipe = !m[2].nil?
75
+ dv = if !has_pipe
76
+ NULL_VALUE
77
+ else
78
+ m[3]
79
+ end
80
+ result = ENV.include?(var_name) ? ENV[var_name] : dv
81
+ else
82
+ m = COLON_OBJECT_PATTERN.match str
83
+ if !m.nil?
84
+ require(m[1][0..-4]) unless m[1].nil?
85
+ result = Object.const_get m[3]
86
+ unless m[5].nil?
87
+ parts = m[6].split('.')
88
+ parts.each do |part|
89
+ result = result.send(part)
90
+ end
91
+ end
92
+ else
93
+ m = INTERPOLATION_PATTERN.match str
94
+ unless m.nil?
95
+ matches = str.enum_for(:scan, INTERPOLATION_PATTERN).map do
96
+ [Regexp.last_match.offset(0), Regexp.last_match.captures[1]]
97
+ end
98
+ cp = 0
99
+ failed = false
100
+ parts = []
101
+ matches.each do |off, path|
102
+ first = off[0]
103
+ last = off[1]
104
+ parts.push str[cp..first - 1] if first > cp
105
+ begin
106
+ parts.push string_for(cfg.get(path))
107
+ cp = last
108
+ rescue StandardError
109
+ failed = true
110
+ break
111
+ end
112
+ end
113
+ unless failed
114
+ parts.push str[cp..str.length - 1] if cp < str.length
115
+ result = parts.join('')
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ result
122
+ end
123
+ end
124
+
125
+ class RecognizerError < StandardError
126
+ attr_accessor :location
127
+ end
128
+
129
+ class TokenizerError < RecognizerError; end
130
+
131
+ class ParserError < RecognizerError; end
132
+
133
+ class ConfigError < RecognizerError; end
134
+
135
+ class InvalidPathError < ConfigError; end
136
+
137
+ class BadIndexError < ConfigError; end
138
+
139
+ class CircularReferenceError < ConfigError; end
140
+
141
+ class Location
142
+ attr_accessor :line
143
+ attr_accessor :column
144
+
145
+ def self.from(other)
146
+ Location.new other.line, other.column
147
+ end
148
+
149
+ def initialize(line = 1, column = 1)
150
+ @line = line
151
+ @column = column
152
+ end
153
+
154
+ def next_line
155
+ self.line += 1
156
+ self.column = 1
157
+ end
158
+
159
+ def update(other)
160
+ @line = other.line
161
+ @column = other.column
162
+ end
163
+
164
+ def to_s
165
+ "(#{@line}, #{@column})"
166
+ end
167
+
168
+ def ==(other)
169
+ @line == other.line && @column == other.column
170
+ end
171
+ end
172
+
173
+ # Kinds of token
174
+ # :EOF
175
+ # :WORD
176
+ # :INTEGER
177
+ # :FLOAT
178
+ # :STRING
179
+ # :NEWLINE
180
+ # :LEFT_CURLY
181
+ # :RIGHT_CURLY
182
+ # :LEFT_BRACKET
183
+ # :RIGHT_BRACKET
184
+ # :LEFT_PARENTHESIS
185
+ # :RIGHT_PARENTHESIS
186
+ # :LESS_THAN
187
+ # :GREATER_THAN
188
+ # :LESS_THAN_OR_EQUAL
189
+ # :GREATER_THAN_OR_EQUAL
190
+ # :ASSIGN
191
+ # :EQUAL
192
+ # :UNEQUAL
193
+ # :ALT_UNEQUAL
194
+ # :LEFT_SHIFT
195
+ # :RIGHT_SHIFT
196
+ # :DOT
197
+ # :COMMA
198
+ # :COLON
199
+ # :AT
200
+ # :PLUS
201
+ # :MINUS
202
+ # :STAR
203
+ # :POWER
204
+ # :SLASH
205
+ # :SLASH_SLASH
206
+ # :MODULO
207
+ # :BACKTICK
208
+ # :DOLLAR
209
+ # :TRUE
210
+ # :FALSE
211
+ # :NONE
212
+ # :IS
213
+ # :IN
214
+ # :NOT
215
+ # :AND
216
+ # :OR
217
+ # :BITWISE_AND
218
+ # :BITWISE_OR
219
+ # :BITWISE_XOR
220
+ # :BITWISE_COMPLEMENT
221
+ # :COMPLEX
222
+ # :IS_NOT
223
+ # :NOT_IN
224
+
225
+ class ASTNode
226
+ attr_accessor :kind
227
+ attr_accessor :start
228
+ attr_accessor :end
229
+
230
+ def initialize(kind)
231
+ @kind = kind
232
+ end
233
+ end
234
+
235
+ class Token < ASTNode
236
+ attr_accessor :text
237
+ attr_accessor :value
238
+
239
+ def initialize(kind, text, value = nil)
240
+ super(kind)
241
+ @text = text
242
+ @value = value
243
+ end
244
+
245
+ def to_s
246
+ "Token(#{@kind}, #{text.inspect}, #{value.inspect})"
247
+ end
248
+
249
+ def ==(other)
250
+ @kind == other.kind && @text == other.text && @value == other.value &&
251
+ @start == other.start && @end == other.end
252
+ end
253
+ end
254
+
255
+ PUNCTUATION = {
256
+ ':' => :COLON,
257
+ '-' => :MINUS,
258
+ '+' => :PLUS,
259
+ '*' => :STAR,
260
+ '/' => :SLASH,
261
+ '%' => :MODULO,
262
+ ',' => :COMMA,
263
+ '{' => :LEFT_CURLY,
264
+ '}' => :RIGHT_CURLY,
265
+ '[' => :LEFT_BRACKET,
266
+ ']' => :RIGHT_BRACKET,
267
+ '(' => :LEFT_PARENTHESIS,
268
+ ')' => :RIGHT_PARENTHESIS,
269
+ '@' => :AT,
270
+ '$' => :DOLLAR,
271
+ '<' => :LESS_THAN,
272
+ '>' => :GREATER_THAN,
273
+ '!' => :NOT,
274
+ '~' => :BITWISE_COMPLEMENT,
275
+ '&' => :BITWISE_AND,
276
+ '|' => :BITWISE_OR,
277
+ '^' => :BITWISE_XOR,
278
+ '.' => :DOT
279
+ }.freeze
280
+
281
+ KEYWORDS = {
282
+ 'true' => :TRUE,
283
+ 'false' => :FALSE,
284
+ 'null' => :NONE,
285
+ 'is' => :IS,
286
+ 'in' => :IN,
287
+ 'not' => :NOT,
288
+ 'and' => :AND,
289
+ 'or' => :OR
290
+ }.freeze
291
+
292
+ ESCAPES = {
293
+ 'a' => "\u0007", # rubocop: disable Layout/HashAlignment
294
+ 'b' => "\b", # rubocop: disable Layout/HashAlignment
295
+ 'f' => "\u000C", # rubocop:disable Layout/HashAlignment
296
+ 'n' => "\n", # rubocop:disable Layout/HashAlignment
297
+ 'r' => "\r", # rubocop:disable Layout/HashAlignment
298
+ 't' => "\t", # rubocop:disable Layout/HashAlignment
299
+ 'v' => "\u000B", # rubocop:disable Layout/HashAlignment
300
+ '\\' => '\\',
301
+ '\'' => "'",
302
+ '"' => '"' # rubocop:disable Layout/HashAlignment
303
+ }.freeze
304
+
305
+ NULL_VALUE = Object.new
306
+
307
+ KEYWORD_VALUES = {
308
+ TRUE: true,
309
+ FALSE: false,
310
+ NONE: NULL_VALUE
311
+ }.freeze
312
+
313
+ class Tokenizer
314
+ include Utils
315
+
316
+ def initialize(stream)
317
+ @stream = stream
318
+ @location = Location.new
319
+ @char_location = Location.new
320
+ @pushed_back = []
321
+ end
322
+
323
+ def tokens
324
+ result = []
325
+ loop do
326
+ t = get_token
327
+ result.push t
328
+ break if t.kind == :EOF
329
+ end
330
+ result
331
+ end
332
+
333
+ def push_back(chr)
334
+ @pushed_back.push([chr, Location.from(@char_location)]) unless chr.nil?
335
+ end
336
+
337
+ def get_char
338
+ if !@pushed_back.empty?
339
+ result, loc = @pushed_back.pop
340
+ @char_location.update loc
341
+ @location.update loc # will be bumped later
342
+ else
343
+ @char_location.update @location
344
+ result = @stream.getc
345
+ end
346
+ unless result.nil?
347
+ if result == "\n"
348
+ @location.next_line
349
+ else
350
+ @location.column += 1
351
+ end
352
+ end
353
+ result
354
+ end
355
+
356
+ def get_number(text, startloc, endloc)
357
+ kind = :INTEGER
358
+ in_exponent = false
359
+ radix = 0
360
+ dot_seen = !text.index('.').nil?
361
+ last_was_digit = digit?(text[-1])
362
+
363
+ while true
364
+ c = get_char
365
+
366
+ break if c.nil?
367
+
368
+ dot_seen = true if c == '.'
369
+ if c == '_'
370
+ if last_was_digit
371
+ text = append_char text, c, endloc
372
+ last_was_digit = false
373
+ next
374
+ end
375
+ e = TokenizerError.new "Invalid '_' in number: #{text}#{c}"
376
+
377
+ e.location = @char_location
378
+ raise e
379
+ end
380
+ last_was_digit = false # unless set in one of the clauses below
381
+ if (radix.zero? && (c >= '0') && (c <= '9')) ||
382
+ ((radix == 2) && (c >= '0') && (c <= '1')) ||
383
+ ((radix == 8) && (c >= '0') && (c <= '7')) ||
384
+ ((radix == 16) && hexdigit?(c))
385
+ text = append_char text, c, endloc
386
+ last_was_digit = true
387
+ elsif ((c == 'o') || (c == 'O') || (c == 'x') ||
388
+ (c == 'X') || (c == 'b') || (c == 'B')) &&
389
+ (text.length == 1) && (text[0] == '0')
390
+ radix = if c.upcase == 'X'
391
+ 16
392
+ else
393
+ (c == 'o') || (c == 'O') ? 8 : 2
394
+ end
395
+ text = append_char text, c, endloc
396
+ elsif radix.zero? && (c == '.') && !in_exponent && text.index(c).nil?
397
+ text = append_char text, c, endloc
398
+ elsif radix.zero? && (c == '-') && text.index('-', 1).nil? && in_exponent
399
+ text = append_char text, c, endloc
400
+ elsif radix.zero? && ((c == 'e') || (c == 'E')) && text.index('e').nil? &&
401
+ text.index('E').nil? && (text[-1] != '_')
402
+ text = append_char text, c, endloc
403
+ in_exponent = true
404
+ else
405
+ break
406
+ end
407
+ end
408
+
409
+ # Reached the end of the actual number part. Before checking
410
+ # for complex, ensure that the last char wasn't an underscore.
411
+ if text[-1] == '_'
412
+ e = TokenizerError.new "Invalid '_' at end of number: #{text}"
413
+
414
+ e.location = endloc
415
+ raise e
416
+ end
417
+ if radix.zero? && ((c == 'j') || (c == 'J'))
418
+ text = append_char text, c, endloc
419
+ kind = :COMPLEX
420
+ else
421
+ # not allowed to have a letter or digit which wasn't accepted
422
+ if (c != '.') && !alnum?(c) # rubocop:disable Style/IfInsideElse
423
+ push_back c
424
+ else
425
+ e = TokenizerError.new "Invalid character in number: #{c}"
426
+
427
+ e.location = @char_location
428
+ raise e
429
+ end
430
+ end
431
+
432
+ s = text.gsub(/_/, '')
433
+
434
+ if radix != 0
435
+ value = Integer s[2..-1], radix
436
+ elsif kind == :COMPLEX
437
+ imaginary = s[0..-2].to_f
438
+ value = Complex(0.0, imaginary)
439
+ elsif in_exponent || dot_seen
440
+ kind = :FLOAT
441
+ value = s.to_f
442
+ else
443
+ radix = s[0] == '0' ? 8 : 10
444
+ begin
445
+ value = Integer s, radix
446
+ rescue ArgumentError
447
+ e = TokenizerError.new "Invalid character in number: #{s}"
448
+ e.location = startloc
449
+ raise e
450
+ end
451
+ end
452
+ [text, kind, value]
453
+ end
454
+
455
+ def parse_escapes(str)
456
+ i = str.index '\\'
457
+ if i.nil?
458
+ result = str
459
+ else
460
+ failed = false
461
+ result = ''
462
+ until i.nil?
463
+ result += str[0..i - 1] if i.positive?
464
+ c = str[i + 1]
465
+ if ESCAPES.key?(c)
466
+ result += ESCAPES[c]
467
+ i += 2
468
+ elsif c =~ /[xu]/i
469
+ slen = if c.upcase == 'X'
470
+ 4
471
+ else
472
+ c == 'u' ? 6 : 10
473
+ end
474
+ if i + slen > str.length
475
+ failed = true
476
+ break
477
+ end
478
+ p = str[i + 2..i + slen - 1]
479
+ if p =~ /^[[:xdigit:]]$/i
480
+ failed = true
481
+ break
482
+ end
483
+ begin
484
+ j = Integer p, 16
485
+ rescue ArgumentError
486
+ failed = true
487
+ break
488
+ end
489
+ if j.between?(0xd800, 0xdfff) || (j >= 0x110000)
490
+ failed = true
491
+ break
492
+ end
493
+ result += j.chr 'utf-8'
494
+ i += slen
495
+ else
496
+ failed = true
497
+ break
498
+ end
499
+ str = str[i..-1]
500
+ i = str.index '\\'
501
+ end
502
+ if !failed
503
+ result += str
504
+ else
505
+ e = TokenizerError.new "Invalid escape sequence at index #{i}"
506
+ raise e
507
+ end
508
+ end
509
+ result
510
+ end
511
+
512
+ def append_char(token, chr, end_location)
513
+ token += chr
514
+ end_location.update @char_location
515
+ token
516
+ end
517
+
518
+ def get_token
519
+ start_location = Location.new
520
+ end_location = Location.new
521
+ kind = :EOF
522
+ token = ''
523
+ value = nil
524
+
525
+ loop do
526
+ c = get_char
527
+
528
+ start_location.update @char_location
529
+ end_location.update @char_location
530
+
531
+ break if c.nil?
532
+
533
+ if c == '#'
534
+ token += c + @stream.readline.rstrip
535
+ kind = :NEWLINE
536
+ @location.next_line
537
+ end_location.update(@location)
538
+ end_location.column -= 1
539
+ break
540
+ elsif c == "\n"
541
+ token += c
542
+ end_location.update @location
543
+ end_location.column -= 1
544
+ kind = :NEWLINE
545
+ break
546
+ elsif c == "\r"
547
+ c = get_char
548
+ push_back c if c != "\n"
549
+ kind = :NEWLINE
550
+ break
551
+ elsif c == '\\'
552
+ c = get_char
553
+ if c != "\n"
554
+ e = TokenizerError.new 'Unexpected character: \\'
555
+ e.location = @char_location
556
+ raise e
557
+ end
558
+ end_location.update @char_location
559
+ next
560
+ elsif white_space?(c)
561
+ next
562
+ elsif c == '_' || letter?(c)
563
+ kind = :WORD
564
+ token = append_char token, c, end_location
565
+ c = get_char
566
+ while !c.nil? && (alnum?(c) || (c == '_'))
567
+ token = append_char token, c, end_location
568
+ c = get_char
569
+ end
570
+ push_back c
571
+ value = token
572
+ if KEYWORDS.key?(value)
573
+ kind = KEYWORDS[value]
574
+ value = KEYWORD_VALUES[kind] if KEYWORD_VALUES.key?(kind)
575
+ end
576
+ break
577
+ elsif c == '`'
578
+ kind = :BACKTICK
579
+ token = append_char token, c, end_location
580
+ loop do
581
+ c = get_char
582
+ break if c.nil?
583
+
584
+ token = append_char token, c, end_location
585
+ break if c == '`'
586
+ end
587
+ if c.nil?
588
+ e = TokenizerError.new "Unterminated `-string: #{token}"
589
+ e.location = start_location
590
+ raise e
591
+ end
592
+ begin
593
+ value = parse_escapes token[1..token.length - 2]
594
+ rescue RecognizerError
595
+ e.location = start_location
596
+ raise e
597
+ end
598
+ break
599
+ elsif !'"\''.index(c).nil?
600
+ quote = c
601
+ multi_line = false
602
+ escaped = false
603
+ kind = :STRING
604
+
605
+ token = append_char token, c, end_location
606
+ c1 = get_char
607
+ c1_loc = Location.from @char_location
608
+
609
+ if c1 != quote
610
+ push_back c1
611
+ else
612
+ c2 = get_char
613
+ if c2 != quote
614
+ push_back c2
615
+ @char_location.update c1_loc if c2.nil?
616
+ push_back c1
617
+ else
618
+ multi_line = true
619
+ token = append_char token, quote, end_location
620
+ token = append_char token, quote, end_location
621
+ end
622
+ end
623
+
624
+ quoter = token[0..-1]
625
+
626
+ loop do
627
+ c = get_char
628
+ break if c.nil?
629
+
630
+ token = append_char token, c, end_location
631
+ if (c == quote) && !escaped
632
+ n = token.length
633
+
634
+ break if !multi_line || (n >= 6) && (token[n - 3..n - 1] == quoter) && token[n - 4] != '\\'
635
+ end
636
+ escaped = c == '\\' ? !escaped : false
637
+ end
638
+ if c.nil?
639
+ e = TokenizerError.new "Unterminated quoted string: #{token}"
640
+
641
+ e.location = start_location
642
+ raise e
643
+ end
644
+ n = quoter.length
645
+ begin
646
+ value = parse_escapes token[n..token.length - n - 1]
647
+ rescue RecognizerError => e
648
+ e.location = start_location
649
+ raise e
650
+ end
651
+ break
652
+ elsif digit?(c)
653
+ token = append_char token, c, end_location
654
+ token, kind, value = get_number token, start_location, end_location
655
+ break
656
+ elsif c == '='
657
+ nc = get_char
658
+
659
+ if nc != '='
660
+ kind = :ASSIGN
661
+ token += c
662
+ push_back nc
663
+ else
664
+ kind = :EQUAL
665
+ token += c
666
+ token = append_char token, c, end_location
667
+ end
668
+ break
669
+ elsif PUNCTUATION.key?(c)
670
+ kind = PUNCTUATION[c]
671
+ token = append_char token, c, end_location
672
+ if c == '.'
673
+ c = get_char
674
+ if !digit?(c)
675
+ push_back c
676
+ else
677
+ token = append_char token, c, end_location
678
+ token, kind, value = get_number token, start_location, end_location
679
+ end
680
+ elsif c == '-'
681
+ c = get_char
682
+ if !digit?(c) && (c != '.')
683
+ push_back c
684
+ else
685
+ token = append_char token, c, end_location
686
+ token, kind, value = get_number token, start_location, end_location
687
+ end
688
+ elsif c == '<'
689
+ c = get_char
690
+ if c == '='
691
+ kind = :LESS_THAN_OR_EQUAL
692
+ token = append_char token, c, end_location
693
+ elsif c == '>'
694
+ kind = :ALT_UNEQUAL
695
+ token = append_char token, c, end_location
696
+ elsif c == '<'
697
+ kind = :LEFT_SHIFT
698
+ token = append_char token, c, end_location
699
+ else
700
+ push_back c
701
+ end
702
+ elsif c == '>'
703
+ c = get_char
704
+ if c == '='
705
+ kind = :GREATER_THAN_OR_EQUAL
706
+ token = append_char token, c, end_location
707
+ elsif c == '>'
708
+ kind = :RIGHT_SHIFT
709
+ token = append_char token, c, end_location
710
+ else
711
+ push_back c
712
+ end
713
+ elsif c == '!'
714
+ c = get_char
715
+ if c == '='
716
+ kind = :UNEQUAL
717
+ token = append_char token, c, end_location
718
+ else
719
+ push_back c
720
+ end
721
+ elsif c == '/'
722
+ c = get_char
723
+ if c != '/'
724
+ push_back c
725
+ else
726
+ kind = :SLASH_SLASH
727
+ token = append_char token, c, end_location
728
+ end
729
+ elsif c == '*'
730
+ c = get_char
731
+ if c != '*'
732
+ push_back c
733
+ else
734
+ kind = :POWER
735
+ token = append_char token, c, end_location
736
+ end
737
+ elsif (c == '&') || (c == '|')
738
+ c2 = get_char
739
+
740
+ if c2 != c
741
+ push_back c2
742
+ else
743
+ kind = c2 == '&' ? :AND : :OR
744
+ token = append_char token, c, end_location
745
+ end
746
+ end
747
+ break
748
+ else
749
+ e = TokenizerError.new "Unexpected character: #{c}"
750
+ e.location = @char_location
751
+ raise e
752
+ end
753
+ end
754
+ result = Token.new kind, token, value
755
+ result.start = Location.from start_location
756
+ result.end = Location.from end_location
757
+ result
758
+ end
759
+ end
760
+
761
+ def make_tokenizer(src)
762
+ stream = StringIO.new src, 'r:utf-8'
763
+ Tokenizer.new stream
764
+ end
765
+
766
+ class UnaryNode < ASTNode
767
+ attr_reader :operand
768
+
769
+ def initialize(kind, operand)
770
+ super(kind)
771
+ @operand = operand
772
+ end
773
+
774
+ def to_s
775
+ "UnaryNode(#{@kind}, #{@operand})"
776
+ end
777
+
778
+ def ==(other)
779
+ @kind == other.kind && @operand == other.operand
780
+ end
781
+ end
782
+
783
+ class BinaryNode < ASTNode
784
+ attr_reader :lhs
785
+ attr_reader :rhs
786
+
787
+ def initialize(kind, lhs, rhs)
788
+ super(kind)
789
+ @lhs = lhs
790
+ @rhs = rhs
791
+ end
792
+
793
+ def to_s
794
+ "BinaryNode(#{@kind}, #{@lhs}, #{@rhs})"
795
+ end
796
+
797
+ def ==(other)
798
+ @kind == other.kind && @lhs == other.lhs && @rhs == other.rhs
799
+ end
800
+ end
801
+
802
+ class SliceNode < ASTNode
803
+ attr_reader :start_index
804
+ attr_reader :stop_index
805
+ attr_reader :step
806
+
807
+ def initialize(start_index, stop_index, step)
808
+ super(:COLON)
809
+ @start_index = start_index
810
+ @stop_index = stop_index
811
+ @step = step
812
+ end
813
+
814
+ def to_s
815
+ "SliceNode(#{@start_index}:#{@stop_index}:#{@step})"
816
+ end
817
+
818
+ def ==(other)
819
+ @kind == other.kind && @start_index == other.start_index &&
820
+ @stop_index == other.stop_index && @step == other.step
821
+ end
822
+ end
823
+
824
+ class ListNode < ASTNode
825
+ attr_reader :elements
826
+
827
+ def initialize(elements)
828
+ super(:LEFT_BRACKET)
829
+ @elements = elements
830
+ end
831
+ end
832
+
833
+ class MappingNode < ASTNode
834
+ attr_reader :elements
835
+
836
+ def initialize(elements)
837
+ super(:LEFT_CURLY)
838
+ @elements = elements
839
+ end
840
+ end
841
+
842
+ class Parser
843
+ attr_reader :tokenizer
844
+ attr_reader :next_token
845
+
846
+ def initialize(stream)
847
+ @tokenizer = Tokenizer.new stream
848
+ @next_token = @tokenizer.get_token
849
+ end
850
+
851
+ def at_end
852
+ @next_token.kind == :EOF
853
+ end
854
+
855
+ def advance
856
+ @next_token = @tokenizer.get_token
857
+ @next_token.kind
858
+ end
859
+
860
+ def expect(kind)
861
+ if @next_token.kind != kind
862
+ e = ParserError.new "Expected #{kind} but got #{@next_token.kind}"
863
+ e.location = @next_token.start
864
+ # require 'byebug'; byebug
865
+ raise e
866
+ end
867
+ result = @next_token
868
+ advance
869
+ result
870
+ end
871
+
872
+ def consume_newlines
873
+ result = @next_token.kind
874
+
875
+ result = advance while result == :NEWLINE
876
+ result
877
+ end
878
+
879
+ EXPRESSION_STARTERS = Set[
880
+ :LEFT_CURLY, :LEFT_BRACKET, :LEFT_PARENTHESIS,
881
+ :AT, :DOLLAR, :BACKTICK, :PLUS, :MINUS, :BITWISE_COMPLEMENT,
882
+ :INTEGER, :FLOAT, :COMPLEX, :TRUE, :FALSE,
883
+ :NONE, :NOT, :STRING, :WORD
884
+ ]
885
+
886
+ VALUE_STARTERS = Set[
887
+ :WORD, :INTEGER, :FLOAT, :COMPLEX, :STRING, :BACKTICK,
888
+ :NONE, :TRUE, :FALSE
889
+ ]
890
+
891
+ def strings
892
+ result = @next_token
893
+ if advance == :STRING
894
+ all_text = ''
895
+ all_value = ''
896
+ t = result.text
897
+ v = result.value
898
+ start = result.start
899
+ endpos = result.end
900
+
901
+ loop do
902
+ all_text += t
903
+ all_value += v
904
+ t = @next_token.text
905
+ v = @next_token.value
906
+ endpos = @next_token.end
907
+ kind = advance
908
+ break if kind != :STRING
909
+ end
910
+ all_text += t # the last one
911
+ all_value += v
912
+ result = Token.new :STRING, all_text, all_value
913
+ result.start = start
914
+ result.end = endpos
915
+ end
916
+ result
917
+ end
918
+
919
+ def value
920
+ kind = @next_token.kind
921
+ unless VALUE_STARTERS.include? kind
922
+ e = ParserError.new "Unexpected when looking for value: #{kind}"
923
+ e.location = @next_token.start
924
+ raise e
925
+ end
926
+ if kind == :STRING
927
+ result = strings
928
+ else
929
+ result = @next_token
930
+ advance
931
+ end
932
+ result
933
+ end
934
+
935
+ def atom
936
+ kind = @next_token.kind
937
+ case kind
938
+ when :LEFT_CURLY
939
+ result = mapping
940
+ when :LEFT_BRACKET
941
+ result = list
942
+ when :DOLLAR
943
+ expect :DOLLAR
944
+ expect :LEFT_CURLY
945
+ spos = @next_token.start
946
+ result = UnaryNode.new :DOLLAR, primary
947
+ result.start = spos
948
+ expect :RIGHT_CURLY
949
+ when :WORD, :INTEGER, :FLOAT, :COMPLEX, :STRING, :BACKTICK, :TRUE, :FALSE, :NONE
950
+ result = value
951
+ when :LEFT_PARENTHESIS
952
+ expect :LEFT_PARENTHESIS
953
+ result = expr
954
+ expect :RIGHT_PARENTHESIS
955
+ else
956
+ e = ParserError.new "Unexpected: #{kind}"
957
+ e.location = @next_token.start
958
+ raise e
959
+ end
960
+ result
961
+ end
962
+
963
+ def _invalid_index(num, pos)
964
+ e = ParserError.new "Invalid index at #{pos}: expected 1 expression, found #{num}"
965
+ e.location = pos
966
+ raise e
967
+ end
968
+
969
+ def _get_slice_element
970
+ lb = list_body
971
+ size = lb.elements.length
972
+
973
+ _invalid_index(size, lb.start) unless size == 1
974
+ lb.elements[0]
975
+ end
976
+
977
+ def _try_get_step
978
+ kind = advance
979
+ kind == :RIGHT_BRACKET ? nil : _get_slice_element
980
+ end
981
+
982
+ def trailer
983
+ op = @next_token.kind
984
+
985
+ if op != :LEFT_BRACKET
986
+ expect :DOT
987
+ result = expect :WORD
988
+ else
989
+ kind = advance
990
+ is_slice = false
991
+ start_index = nil
992
+ stop_index = nil
993
+ step = nil
994
+
995
+ if kind == :COLON
996
+ # it's a slice like [:xyz:abc]
997
+ is_slice = true
998
+ else
999
+ elem = _get_slice_element
1000
+
1001
+ kind = @next_token.kind
1002
+ if kind != :COLON
1003
+ result = elem
1004
+ else
1005
+ start_index = elem
1006
+ is_slice = true
1007
+ end
1008
+ end
1009
+ if is_slice
1010
+ op = :COLON
1011
+ # at this point startIndex is either nil (if foo[:xyz]) or a
1012
+ # value representing the start. We are pointing at the COLON
1013
+ # after the start value
1014
+ kind = advance
1015
+ if kind == :COLON # no stop, but there might be a step
1016
+ s = _try_get_step
1017
+ step = s unless s.nil?
1018
+ elsif kind != :RIGHT_BRACKET
1019
+ stop_index = _get_slice_element
1020
+ kind = @next_token.kind
1021
+ if kind == :COLON
1022
+ s = _try_get_step
1023
+ step = s unless s.nil?
1024
+ end
1025
+ end
1026
+ result = SliceNode.new start_index, stop_index, step
1027
+ end
1028
+ expect :RIGHT_BRACKET
1029
+ end
1030
+ [op, result]
1031
+ end
1032
+
1033
+ def primary
1034
+ result = atom
1035
+ kind = @next_token.kind
1036
+ while %i[DOT LEFT_BRACKET].include? kind
1037
+ op, rhs = trailer
1038
+ result = BinaryNode.new op, result, rhs
1039
+ kind = @next_token.kind
1040
+ end
1041
+ result
1042
+ end
1043
+
1044
+ def object_key
1045
+ if @next_token.kind == :STRING
1046
+ result = strings
1047
+ else
1048
+ result = @next_token
1049
+ advance
1050
+ end
1051
+ result
1052
+ end
1053
+
1054
+ def mapping_body
1055
+ result = []
1056
+ kind = consume_newlines
1057
+ spos = @next_token.start
1058
+ if kind != :RIGHT_CURLY && kind != :EOF
1059
+ if kind != :WORD && kind != :STRING
1060
+ e = ParserError.new "Unexpected type for key: #{kind}"
1061
+
1062
+ e.location = @next_token.start
1063
+ raise e
1064
+ end
1065
+ while %i[WORD STRING].include? kind
1066
+ key = object_key
1067
+ kind = @next_token.kind
1068
+ if kind != :COLON && kind != :ASSIGN
1069
+ e = ParserError.new "Expected key-value separator, found: #{kind}"
1070
+
1071
+ e.location = @next_token.start
1072
+ raise e
1073
+ end
1074
+ advance
1075
+ consume_newlines
1076
+ result.push [key, expr]
1077
+ kind = @next_token.kind
1078
+ if %i[NEWLINE COMMA].include? kind
1079
+ advance
1080
+ kind = consume_newlines
1081
+ end
1082
+ end
1083
+ end
1084
+ result = MappingNode.new result
1085
+ result.start = spos
1086
+ result
1087
+ end
1088
+
1089
+ def mapping
1090
+ expect :LEFT_CURLY
1091
+ result = mapping_body
1092
+ expect :RIGHT_CURLY
1093
+ result
1094
+ end
1095
+
1096
+ def list_body
1097
+ result = []
1098
+ kind = consume_newlines
1099
+ spos = @next_token.start
1100
+ while EXPRESSION_STARTERS.include? kind
1101
+ result.push(expr)
1102
+ kind = @next_token.kind
1103
+ break unless %i[NEWLINE COMMA].include? kind
1104
+
1105
+ advance
1106
+ kind = consume_newlines
1107
+ end
1108
+ result = ListNode.new result
1109
+ result.start = spos
1110
+ result
1111
+ end
1112
+
1113
+ def list
1114
+ expect :LEFT_BRACKET
1115
+ result = list_body
1116
+ expect :RIGHT_BRACKET
1117
+ result
1118
+ end
1119
+
1120
+ def container
1121
+ kind = consume_newlines
1122
+
1123
+ case kind
1124
+ when :LEFT_CURLY
1125
+ result = mapping
1126
+ when :LEFT_BRACKET
1127
+ result = list
1128
+ when :WORD, :STRING, :EOF
1129
+ result = mapping_body
1130
+ else
1131
+ e = ParserError.new "Unexpected type for container: #{kind}"
1132
+
1133
+ e.location = @next_token.start
1134
+ raise e
1135
+ end
1136
+ consume_newlines
1137
+ result
1138
+ end
1139
+
1140
+ def power
1141
+ result = primary
1142
+ while @next_token.kind == :POWER
1143
+ advance
1144
+ result = BinaryNode.new :POWER, result, unary_expr
1145
+ end
1146
+ result
1147
+ end
1148
+
1149
+ def unary_expr
1150
+ kind = @next_token.kind
1151
+ spos = @next_token.start
1152
+ result = if !%i[PLUS MINUS BITWISE_COMPLEMENT AT].include? kind
1153
+ power
1154
+ else
1155
+ advance
1156
+ UnaryNode.new kind, unary_expr
1157
+ end
1158
+ result.start = spos
1159
+ result
1160
+ end
1161
+
1162
+ def mul_expr
1163
+ result = unary_expr
1164
+ kind = @next_token.kind
1165
+
1166
+ while %i[STAR SLASH SLASH_SLASH MODULO].include? kind
1167
+ advance
1168
+ result = BinaryNode.new kind, result, unary_expr
1169
+ kind = @next_token.kind
1170
+ end
1171
+ result
1172
+ end
1173
+
1174
+ def add_expr
1175
+ result = mul_expr
1176
+ kind = @next_token.kind
1177
+
1178
+ while %i[PLUS MINUS].include? kind
1179
+ advance
1180
+ result = BinaryNode.new kind, result, mul_expr
1181
+ kind = @next_token.kind
1182
+ end
1183
+ result
1184
+ end
1185
+
1186
+ def shift_expr
1187
+ result = add_expr
1188
+ kind = @next_token.kind
1189
+
1190
+ while %i[LEFT_SHIFT RIGHT_SHIFT].include? kind
1191
+ advance
1192
+ result = BinaryNode.new kind, result, add_expr
1193
+ kind = @next_token.kind
1194
+ end
1195
+ result
1196
+ end
1197
+
1198
+ def bitand_expr
1199
+ result = shift_expr
1200
+
1201
+ while @next_token.kind == :BITWISE_AND
1202
+ advance
1203
+ result = BinaryNode.new :BITWISE_AND, result, shift_expr
1204
+ end
1205
+ result
1206
+ end
1207
+
1208
+ def bitxor_expr
1209
+ result = bitand_expr
1210
+
1211
+ while @next_token.kind == :BITWISE_XOR
1212
+ advance
1213
+ result = BinaryNode.new :BITWISE_XOR, result, bitand_expr
1214
+ end
1215
+ result
1216
+ end
1217
+
1218
+ def bitor_expr
1219
+ result = bitxor_expr
1220
+
1221
+ while @next_token.kind == :BITWISE_OR
1222
+ advance
1223
+ result = BinaryNode.new :BITWISE_OR, result, bitxor_expr
1224
+ end
1225
+ result
1226
+ end
1227
+
1228
+ def comp_op
1229
+ result = @next_token.kind
1230
+ should_advance = false
1231
+ advance
1232
+ if result == :IS && @next_token.kind == :NOT
1233
+ result = :IS_NOT
1234
+ should_advance = true
1235
+ elsif result == :NOT && @next_token.kind == :IN
1236
+ result = :NOT_IN
1237
+ should_advance = true
1238
+ end
1239
+ advance if should_advance
1240
+ result
1241
+ end
1242
+
1243
+ COMPARISON_OPERATORS = Set[
1244
+ :LESS_THAN, :LESS_THAN_OR_EQUAL, :GREATER_THAN, :GREATER_THAN_OR_EQUAL,
1245
+ :EQUAL, :UNEQUAL, :ALT_UNEQUAL, :IS, :IN, :NOT
1246
+ ]
1247
+
1248
+ def comparison
1249
+ result = bitor_expr
1250
+ while COMPARISON_OPERATORS.include? @next_token.kind
1251
+ op = comp_op
1252
+ result = BinaryNode.new op, result, bitor_expr
1253
+ end
1254
+ result
1255
+ end
1256
+
1257
+ def not_expr
1258
+ if @next_token.kind != :NOT
1259
+ comparison
1260
+ else
1261
+ advance
1262
+ UnaryNode.new :NOT, not_expr
1263
+ end
1264
+ end
1265
+
1266
+ def and_expr
1267
+ result = not_expr
1268
+ while @next_token.kind == :AND
1269
+ advance
1270
+ result = BinaryNode.new :AND, result, not_expr
1271
+ end
1272
+ result
1273
+ end
1274
+
1275
+ def expr
1276
+ result = and_expr
1277
+ while @next_token.kind == :OR
1278
+ advance
1279
+ result = BinaryNode.new :OR, result, and_expr
1280
+ end
1281
+ result
1282
+ end
1283
+ end
1284
+
1285
+ def make_parser(src)
1286
+ stream = StringIO.new src, 'r:utf-8'
1287
+ Parser.new stream
1288
+ end
1289
+
1290
+ def string_for(obj)
1291
+ if obj.is_a? Array
1292
+ parts = obj.map { |it| string_for it }
1293
+ "[#{parts.join ', '}]"
1294
+ elsif obj.is_a? Hash
1295
+ parts = obj.map { |k, v| "#{k}: #{string_for v}" }
1296
+ "{#{parts.join ', '}}"
1297
+ else
1298
+ obj.to_s
1299
+ end
1300
+ end
1301
+
1302
+ class Evaluator
1303
+ attr_reader :config
1304
+ attr_reader :refs_seen
1305
+
1306
+ def initialize(config)
1307
+ @config = config
1308
+ @refs_seen = Set.new
1309
+ end
1310
+
1311
+ def eval_at(node)
1312
+ fn = evaluate node.operand
1313
+ unless fn.is_a? String
1314
+ e = ConfigError.new "@ operand must be a string, but is #{fn}"
1315
+ e.location = node.operand.start
1316
+ raise e
1317
+ end
1318
+ found = false
1319
+ p = Pathname.new fn
1320
+ if p.absolute? && p.exist?
1321
+ found = true
1322
+ else
1323
+ p = Pathname.new(@config.root_dir).join fn
1324
+ if p.exist?
1325
+ found = true
1326
+ else
1327
+ @config.include_path.each do |d|
1328
+ p = Pathname.new(d).join(fn)
1329
+ if p.exist?
1330
+ found = true
1331
+ break
1332
+ end
1333
+ end
1334
+ end
1335
+ end
1336
+ unless found
1337
+ e = ConfigError.new "Unable to locate #{fn}"
1338
+ e.location = node.operand.start
1339
+ raise e
1340
+ end
1341
+ f = File.open p, 'r:utf-8'
1342
+ parser = Parser.new f
1343
+ cnode = parser.container
1344
+ if !cnode.is_a? MappingNode
1345
+ result = cnode
1346
+ else
1347
+ result = Config.new
1348
+ result.no_duplicates = @config.no_duplicates
1349
+ result.strict_conversions = @config.strict_conversions
1350
+ result.context = @config.context
1351
+ result.cached = @config.cached
1352
+ result.set_path p
1353
+ result.parent = @config
1354
+ result.include_path = @config.include_path
1355
+ result.data = result.wrap_mapping cnode
1356
+ end
1357
+ result
1358
+ end
1359
+
1360
+ def eval_reference(node)
1361
+ get_from_path node.operand
1362
+ end
1363
+
1364
+ def to_complex(num)
1365
+ if num.is_a? Complex
1366
+ num
1367
+ elsif num.is_a? Numeric
1368
+ Complex(num, 0)
1369
+ else
1370
+ raise ConfigError, "cannot convert #{num} to a complex number"
1371
+ end
1372
+ end
1373
+
1374
+ def to_float(num)
1375
+ if num.is_a? Float
1376
+ num
1377
+ elsif num.is_a? Numeric
1378
+ num.to_f
1379
+ else
1380
+ raise ConfigError, "cannot convert #{num} to a floating-point number"
1381
+ end
1382
+ end
1383
+
1384
+ def merge_dicts(target, source)
1385
+ source.each do |k, v|
1386
+ if target.include?(k) && target[k].is_a?(Hash) && v.is_a?(Hash)
1387
+ merge_dicts target[k], v
1388
+ else
1389
+ target[k] = v
1390
+ end
1391
+ end
1392
+ end
1393
+
1394
+ def merge_dict_wrappers(lhs, rhs)
1395
+ r = lhs.as_dict
1396
+ source = rhs.as_dict
1397
+ merge_dicts r, source
1398
+ result = DictWrapper.new @config
1399
+ result.update r
1400
+ result
1401
+ end
1402
+
1403
+ def eval_add(node)
1404
+ lhs = evaluate(node.lhs)
1405
+ rhs = evaluate(node.rhs)
1406
+ result = nil
1407
+ if lhs.is_a?(DictWrapper) && rhs.is_a?(DictWrapper)
1408
+ result = merge_dict_wrappers lhs, rhs
1409
+ elsif lhs.is_a?(ListWrapper) && rhs.is_a?(ListWrapper)
1410
+ result = ListWrapper.new lhs.config
1411
+ result.concat lhs.as_list
1412
+ result.concat rhs.as_list
1413
+ elsif lhs.is_a?(String) && rhs.is_a?(String)
1414
+ result = lhs + rhs
1415
+ elsif lhs.is_a?(Numeric) && rhs.is_a?(Numeric)
1416
+ result = lhs + rhs
1417
+ elsif lhs.is_a?(Complex) || rhs.is_a?(Complex)
1418
+ result = to_complex(lhs) + to_complex(rhs)
1419
+ else
1420
+ raise ConfigError, "cannot add #{rhs} to #{lhs}"
1421
+ end
1422
+ result
1423
+ end
1424
+
1425
+ def eval_subtract(node)
1426
+ lhs = evaluate(node.lhs)
1427
+ rhs = evaluate(node.rhs)
1428
+ result = nil
1429
+ if lhs.is_a?(DictWrapper) && rhs.is_a?(DictWrapper)
1430
+ result = DictWrapper.new @config
1431
+ r = lhs.as_dict
1432
+ s = rhs.as_dict
1433
+ r.each do |k, v|
1434
+ result[k] = v unless s.include? k
1435
+ end
1436
+ elsif lhs.is_a?(Numeric) && rhs.is_a?(Numeric)
1437
+ result = lhs - rhs
1438
+ elsif lhs.is_a?(ListWrapper) && rhs.is_a?(ListWrapper)
1439
+ raise NotImplementedError
1440
+ elsif lhs.is_a?(Complex) || rhs.is_a?(Complex)
1441
+ result = to_complex(lhs) - to_complex(rhs)
1442
+ else
1443
+ raise ConfigError, "unable to add #{lhs} and #{rhs}"
1444
+ end
1445
+ result
1446
+ end
1447
+
1448
+ def eval_multiply(node)
1449
+ lhs = evaluate(node.lhs)
1450
+ rhs = evaluate(node.rhs)
1451
+ result = nil
1452
+ if lhs.is_a?(Numeric) && rhs.is_a?(Numeric)
1453
+ result = lhs * rhs
1454
+ elsif lhs.is_a?(Complex) || rhs.is_a?(Complex)
1455
+ result = to_complex(lhs) * to_complex(rhs)
1456
+ else
1457
+ raise ConfigError, "unable to multiply #{lhs} by #{rhs}"
1458
+ end
1459
+ result
1460
+ end
1461
+
1462
+ def eval_divide(node)
1463
+ lhs = evaluate(node.lhs)
1464
+ rhs = evaluate(node.rhs)
1465
+ result = nil
1466
+ if lhs.is_a?(Numeric) && rhs.is_a?(Numeric)
1467
+ result = to_float(lhs) / rhs
1468
+ elsif lhs.is_a?(Complex) || rhs.is_a?(Complex)
1469
+ result = to_complex(lhs) / to_complex(rhs)
1470
+ else
1471
+ raise ConfigError, "unable to divide #{lhs} by #{rhs}"
1472
+ end
1473
+ result
1474
+ end
1475
+
1476
+ def eval_integer_divide(node)
1477
+ lhs = evaluate(node.lhs)
1478
+ rhs = evaluate(node.rhs)
1479
+ raise ConfigError, "unable to integer-divide #{lhs} by #{rhs}" unless lhs.is_a?(Integer) && rhs.is_a?(Integer)
1480
+
1481
+ lhs / rhs
1482
+ end
1483
+
1484
+ def eval_modulo(node)
1485
+ lhs = evaluate(node.lhs)
1486
+ rhs = evaluate(node.rhs)
1487
+ raise ConfigError, "unable to compute #{lhs} modulo #{rhs}" unless lhs.is_a?(Integer) && rhs.is_a?(Integer)
1488
+
1489
+ lhs % rhs
1490
+ end
1491
+
1492
+ def eval_left_shift(node)
1493
+ lhs = evaluate(node.lhs)
1494
+ rhs = evaluate(node.rhs)
1495
+ raise ConfigError, "unable to left-shift #{lhs} by #{rhs}" unless lhs.is_a?(Integer) && rhs.is_a?(Integer)
1496
+
1497
+ lhs << rhs
1498
+ end
1499
+
1500
+ def eval_right_shift(node)
1501
+ lhs = evaluate(node.lhs)
1502
+ rhs = evaluate(node.rhs)
1503
+ raise ConfigError, "unable to right-shift #{lhs} by #{rhs}" unless lhs.is_a?(Integer) && rhs.is_a?(Integer)
1504
+
1505
+ lhs >> rhs
1506
+ end
1507
+
1508
+ def eval_logical_and(node)
1509
+ lhs = !!evaluate(node.lhs) # rubocop:disable Style/DoubleNegation
1510
+ return false unless lhs
1511
+
1512
+ !!evaluate(node.rhs)
1513
+ end
1514
+
1515
+ def eval_logical_or(node)
1516
+ lhs = !!evaluate(node.lhs) # rubocop:disable Style/DoubleNegation
1517
+ return true if lhs
1518
+
1519
+ !!evaluate(node.rhs)
1520
+ end
1521
+
1522
+ def eval_bitwise_or(node)
1523
+ lhs = evaluate(node.lhs)
1524
+ rhs = evaluate(node.rhs)
1525
+ result = nil
1526
+ if lhs.is_a?(DictWrapper) && rhs.is_a?(DictWrapper)
1527
+ result = merge_dict_wrappers lhs, rhs
1528
+ elsif lhs.is_a?(Integer) && rhs.is_a?(Integer)
1529
+ result = lhs | rhs
1530
+ else
1531
+ raise ConfigError, "unable to bitwise-or #{lhs} and #{rhs}"
1532
+ end
1533
+ result
1534
+ end
1535
+
1536
+ def eval_bitwise_and(node)
1537
+ lhs = evaluate(node.lhs)
1538
+ rhs = evaluate(node.rhs)
1539
+ raise ConfigError, "unable to bitwise-and #{lhs} and #{rhs}" unless lhs.is_a?(Integer) && rhs.is_a?(Integer)
1540
+
1541
+ lhs & rhs
1542
+ end
1543
+
1544
+ def eval_bitwise_xor(node)
1545
+ lhs = evaluate(node.lhs)
1546
+ rhs = evaluate(node.rhs)
1547
+ raise ConfigError, "unable to bitwise-xor #{lhs} and #{rhs}" unless lhs.is_a?(Integer) && rhs.is_a?(Integer)
1548
+
1549
+ lhs ^ rhs
1550
+ end
1551
+
1552
+ def negate(node)
1553
+ operand = evaluate(node.operand)
1554
+ result = nil
1555
+ if operand.is_a?(Integer)
1556
+ result = -operand
1557
+ elsif operand.is_a? Complex
1558
+ result = Complex(-operand.real, -operand.imag)
1559
+ else
1560
+ raise ConfigError, "unable to negate #{operand}"
1561
+ end
1562
+ result
1563
+ end
1564
+
1565
+ def eval_power(node)
1566
+ lhs = evaluate(node.lhs)
1567
+ rhs = evaluate(node.rhs)
1568
+ raise ConfigError, "unable to raise #{lhs} to power #{rhs}" unless lhs.is_a?(Numeric) && rhs.is_a?(Numeric)
1569
+
1570
+ lhs**rhs
1571
+ end
1572
+
1573
+ def evaluate(node)
1574
+ if node.is_a? Token
1575
+ value = node.value
1576
+ if SCALAR_TOKENS.include? node.kind
1577
+ result = value
1578
+ elsif node.kind == :WORD
1579
+ if @config.context.include? value
1580
+ result = @config.context[value]
1581
+ else
1582
+ e = ConfigError.new "Unknown variable '#{value}'"
1583
+ e.location = node.start
1584
+ raise e
1585
+ end
1586
+ elsif node.kind == :BACKTICK
1587
+ result = @config.convert_string value
1588
+ else
1589
+ e = ConfigError.new "Unable to evaluate #{node}"
1590
+ e.location = node.start
1591
+ raise e
1592
+ end
1593
+ elsif node.is_a? MappingNode
1594
+ result = @config.wrap_mapping node
1595
+ elsif node.is_a? ListNode
1596
+ result = @config.wrap_list node
1597
+ else
1598
+ case node.kind
1599
+ when :AT
1600
+ result = eval_at node
1601
+ when :DOLLAR
1602
+ result = eval_reference node
1603
+ when :LEFT_CURLY
1604
+ result = @config.wrap_mapping node
1605
+ when :PLUS
1606
+ result = eval_add node
1607
+ when :MINUS
1608
+ result = if node.is_a? BinaryNode
1609
+ eval_subtract node
1610
+ else
1611
+ negate node
1612
+ end
1613
+ when :STAR
1614
+ result = eval_multiply node
1615
+ when :SLASH
1616
+ result = eval_divide node
1617
+ when :SLASH_SLASH
1618
+ result = eval_integer_divide node
1619
+ when :MODULO
1620
+ result = eval_modulo node
1621
+ when :LEFT_SHIFT
1622
+ result = eval_left_shift node
1623
+ when :RIGHT_SHIFT
1624
+ result = eval_right_shift node
1625
+ when :POWER
1626
+ result = eval_power node
1627
+ when :AND
1628
+ result = eval_logical_and node
1629
+ when :OR
1630
+ result = eval_logical_or node
1631
+ when :BITWISE_OR
1632
+ result = eval_bitwise_or node
1633
+ when :BITWISE_AND
1634
+ result = eval_bitwise_and node
1635
+ when :BITWISE_XOR
1636
+ result = eval_bitwise_xor node
1637
+ else
1638
+ e = ConfigError.new "Unable to evaluate #{node}"
1639
+ e.location = node.start
1640
+ raise e
1641
+ end
1642
+ end
1643
+ result
1644
+ end
1645
+
1646
+ def get_slice(container, slice)
1647
+ size = container.length
1648
+ step = slice.step.nil? ? 1 : evaluate(slice.step)
1649
+ raise BadIndexError, 'slice step cannot be zero' if step.zero?
1650
+
1651
+ start_index = if slice.start_index.nil?
1652
+ 0
1653
+ else
1654
+ n = evaluate slice.start_index
1655
+ if n.negative?
1656
+ if n >= -size
1657
+ n += size
1658
+ else
1659
+ n = 0
1660
+ end
1661
+ elsif n >= size
1662
+ n = size - 1
1663
+ end
1664
+ n
1665
+ end
1666
+
1667
+ stop_index = if slice.stop_index.nil?
1668
+ size - 1
1669
+ else
1670
+ n = evaluate slice.stop_index
1671
+ if n.negative?
1672
+ if n >= -size
1673
+ n += size
1674
+ else
1675
+ n = 0
1676
+ end
1677
+ end
1678
+ n = size if n > size
1679
+ step.negative? ? (n + 1) : (n - 1)
1680
+ end
1681
+
1682
+ stop_index, start_index = start_index, stop_index if step.negative? && start_index < stop_index
1683
+
1684
+ result = ListWrapper.new @config
1685
+ i = start_index
1686
+ not_done = step.positive? ? i <= stop_index : i >= stop_index
1687
+ while not_done
1688
+ result.push container[i]
1689
+ i += step
1690
+ not_done = step.positive? ? i <= stop_index : i >= stop_index
1691
+ end
1692
+ result
1693
+ end
1694
+
1695
+ def ref?(node)
1696
+ node.is_a?(ASTNode) && node.kind == :DOLLAR
1697
+ end
1698
+
1699
+ def get_from_path(path_node)
1700
+ parts = unpack_path path_node
1701
+ result = @config.base_get parts.shift.value
1702
+
1703
+ # We start the evaluation with the current instance, but a path may
1704
+ # cross sub-configuration boundaries, and references must always be
1705
+ # evaluated in the context of the immediately enclosing configuration,
1706
+ # not the top-level configuration (references are relative to the
1707
+ # root of the enclosing configuration - otherwise configurations would
1708
+ # not be standalone. So whenever we cross a sub-configuration boundary,
1709
+ # the current_evaluator has to be pegged to that sub-configuration.
1710
+
1711
+ current_evaluator = result.is_a?(Config) ? result.evaluator : self
1712
+
1713
+ parts.each do |part|
1714
+ op, operand = part
1715
+ sliced = operand.is_a? SliceNode
1716
+ operand = current_evaluator.evaluate(operand) if !sliced && op != :DOT && operand.is_a?(ASTNode)
1717
+ list_error = sliced && !result.is_a?(ListWrapper)
1718
+ raise BadIndexError, 'slices can only operate on lists' if list_error
1719
+
1720
+ map_error = (result.is_a?(DictWrapper) || result.is_a?(Config)) && !operand.is_a?(String)
1721
+ raise BadIndexError, "string required, but found #{operand}" if map_error
1722
+
1723
+ if result.is_a? DictWrapper
1724
+ raise ConfigError, "Not found in configuration: #{operand}" unless result.include? operand
1725
+
1726
+ result = result.base_get operand
1727
+ elsif result.is_a? Config
1728
+ current_evaluator = result.evaluator
1729
+ result = result.base_get operand
1730
+ elsif result.is_a? ListWrapper
1731
+ n = result.length
1732
+ if operand.is_a? Integer
1733
+ operand += n if operand.negative? && operand >= -n
1734
+ msg = "index out of range: is #{operand}, must be between 0 and #{n - 1}"
1735
+ raise BadIndexError, msg if operand >= n || operand.negative?
1736
+
1737
+ result = result.base_get operand
1738
+ elsif sliced
1739
+ result = get_slice result, operand
1740
+ else
1741
+ raise BadIndexError, "integer required, but found #{operand}"
1742
+ end
1743
+ else
1744
+ # result is not a Config, DictWrapper or ListWrapper.
1745
+ # Just throw a generic "not in configuration" error
1746
+ raise ConfigError, "Not found in configuration: #{to_source path_node}"
1747
+ end
1748
+ if ref? result
1749
+ if current_evaluator.refs_seen.include? result
1750
+ parts = current_evaluator.refs_seen.map { |item| "#{to_source item} #{item.start}" }
1751
+ ps = parts.sort.join(', ')
1752
+ raise CircularReferenceError, "Circular reference: #{ps}"
1753
+ end
1754
+ current_evaluator.refs_seen.add result
1755
+ end
1756
+ if result.is_a? MappingNode
1757
+ result = @config.wrap_mapping result
1758
+ elsif result.is_a? ListNode
1759
+ result = @config.wrap_list result
1760
+ end
1761
+ if result.is_a? ASTNode
1762
+ e = current_evaluator.evaluate result
1763
+ result = e unless e.equal? result
1764
+ end
1765
+ end
1766
+ @refs_seen.clear
1767
+ result
1768
+ end
1769
+ end
1770
+
1771
+ class DictWrapper < Hash
1772
+ attr_reader :config
1773
+
1774
+ alias base_get []
1775
+
1776
+ def initialize(config)
1777
+ @config = config
1778
+ end
1779
+
1780
+ def as_dict
1781
+ result = {}
1782
+
1783
+ each do |k, v|
1784
+ rv = @config.evaluated v
1785
+ if rv.is_a?(DictWrapper) || rv.is_a?(Config)
1786
+ rv = rv.as_dict
1787
+ elsif rv.is_a? ListWrapper
1788
+ rv = rv.as_list
1789
+ end
1790
+ result[k] = rv
1791
+ end
1792
+ result
1793
+ end
1794
+
1795
+ def [](key)
1796
+ raise ConfigError, "Not found in configuration: #{key}" unless include? key
1797
+
1798
+ @config.evaluated base_get(key)
1799
+ end
1800
+ end
1801
+
1802
+ class ListWrapper < Array
1803
+ attr_reader :config
1804
+
1805
+ alias base_get []
1806
+
1807
+ def initialize(config)
1808
+ @config = config
1809
+ end
1810
+
1811
+ def as_list
1812
+ result = []
1813
+ each do |v|
1814
+ rv = @config.evaluated(v)
1815
+ if rv.is_a?(DictWrapper) || rv.is_a?(Config)
1816
+ rv = rv.as_dict
1817
+ elsif rv.is_a? ListWrapper
1818
+ rv = rv.as_list
1819
+ end
1820
+ result.push rv
1821
+ end
1822
+ result
1823
+ end
1824
+
1825
+ def [](index)
1826
+ result = base_get index
1827
+
1828
+ self[index] = result = @config.evaluated(result)
1829
+ result
1830
+ end
1831
+ end
1832
+
1833
+ def unwrap(obj)
1834
+ if obj.is_a? DictWrapper
1835
+ obj.as_dict
1836
+ elsif obj.is_a? ListWrapper
1837
+ obj.as_list
1838
+ else
1839
+ obj
1840
+ end
1841
+ end
1842
+
1843
+ def parse_path(src)
1844
+ parser = make_parser src
1845
+
1846
+ raise InvalidPathError, "Invalid path: #{src}" if parser.next_token.kind != :WORD
1847
+
1848
+ begin
1849
+ result = parser.primary
1850
+ rescue RecognizerError
1851
+ raise InvalidPathError, "Invalid path: #{src}"
1852
+ end
1853
+ raise InvalidPathError, "Invalid path: #{src}" unless parser.at_end
1854
+
1855
+ result
1856
+ end
1857
+
1858
+ def to_source(node)
1859
+ if node.is_a? Token
1860
+ node.value.to_s
1861
+ elsif !node.is_a? ASTNode
1862
+ node.to_s
1863
+ else
1864
+ result = []
1865
+ parts = unpack_path node
1866
+ result.push parts.shift.value
1867
+ parts.each do |part|
1868
+ op, operand = part
1869
+ case op
1870
+ when :DOT
1871
+ result.push ".#{operand}"
1872
+ when :COLON
1873
+ result.append '['
1874
+ result.append to_source(operand.start_index) unless operand.start_index.nil?
1875
+ result.append ':'
1876
+ result.append to_source(operand.stop_index) unless operand.stop_index.nil?
1877
+ result.append ":#{to_source operand.step}" unless operand.step.nil?
1878
+ result.append ']'
1879
+ when :LEFT_BRACKET
1880
+ result.append "[#{to_source operand}]"
1881
+ else
1882
+ raise ConfigError, "unable to compute source for #{node}"
1883
+ end
1884
+ end
1885
+ result.join('')
1886
+ end
1887
+ end
1888
+
1889
+ def _visit(node, collector)
1890
+ if node.is_a? Token
1891
+ collector.push node
1892
+ elsif node.is_a? UnaryNode
1893
+ _visit(node.operand, collector)
1894
+ elsif node.is_a? BinaryNode
1895
+ _visit(node.lhs, collector)
1896
+ case node.kind
1897
+ when :DOT
1898
+ collector.push [node.kind, node.rhs.value]
1899
+ when :COLON # it's a slice
1900
+ collector.push [node.kind, node.rhs]
1901
+ else # it's an array access
1902
+ collector.push [node.kind, node.rhs.value]
1903
+ end
1904
+ end
1905
+ end
1906
+
1907
+ def unpack_path(start)
1908
+ result = []
1909
+ _visit(start, result)
1910
+ result
1911
+ end
1912
+
1913
+ SCALAR_TOKENS = Set[
1914
+ :STRING,
1915
+ :INTEGER,
1916
+ :FLOAT,
1917
+ :COMPLEX,
1918
+ :FALSE,
1919
+ :TRUE,
1920
+ :NONE
1921
+ ]
1922
+
1923
+ class Config
1924
+ include Utils
1925
+
1926
+ attr_accessor :no_duplicates
1927
+ attr_accessor :strict_conversions
1928
+ attr_accessor :context
1929
+ attr_accessor :include_path
1930
+ attr_accessor :path
1931
+ attr_accessor :root_dir
1932
+ attr_accessor :string_converter
1933
+ attr_accessor :parent
1934
+ attr_accessor :data
1935
+ attr_accessor :evaluator
1936
+
1937
+ def initialize(path_or_stream = nil)
1938
+ @no_duplicates = true
1939
+ @strict_conversions = true
1940
+ @context = {}
1941
+ @include_path = []
1942
+ @path = nil
1943
+ @root_dir = nil
1944
+ @evaluator = Evaluator.new self
1945
+ @string_converter = method :default_string_converter
1946
+
1947
+ @cache = nil
1948
+ @data = nil
1949
+ @parent = nil
1950
+
1951
+ return if path_or_stream.nil?
1952
+
1953
+ if path_or_stream.is_a? String
1954
+ load_file path_or_stream
1955
+ else
1956
+ load path_or_stream
1957
+ end
1958
+ end
1959
+
1960
+ def cached
1961
+ !@cache.nil?
1962
+ end
1963
+
1964
+ def cached=(cache)
1965
+ if !cache
1966
+ @cache = nil
1967
+ elsif @cache.nil?
1968
+ @cache = {}
1969
+ end
1970
+ end
1971
+
1972
+ def self.identifier?(str)
1973
+ !/^[\p{L}_]([\p{L}\p{Nd}_]*)$/u.match(str).nil?
1974
+ end
1975
+
1976
+ def load_file(path)
1977
+ f = File.open path, 'r:utf-8'
1978
+ load f
1979
+ f.close
1980
+ end
1981
+
1982
+ def set_path(path)
1983
+ @path = path
1984
+ @root_dir = Pathname.new(path).parent
1985
+ end
1986
+
1987
+ def wrap_mapping(node)
1988
+ result = DictWrapper.new self
1989
+ seen = @no_duplicates ? {} : nil
1990
+ node.elements.each do |t, v|
1991
+ k = t.value
1992
+ unless seen.nil?
1993
+ raise ConfigError, "Duplicate key #{k} seen at #{t.start} (previously at #{seen[k]})" if seen.include? k
1994
+
1995
+ seen[k] = t.start
1996
+ end
1997
+ result[k] = v
1998
+ end
1999
+ result
2000
+ end
2001
+
2002
+ def wrap_list(node)
2003
+ result = ListWrapper.new self
2004
+ result.concat node.elements
2005
+ result
2006
+ end
2007
+
2008
+ def load(stream)
2009
+ parser = Parser.new stream
2010
+ node = parser.container
2011
+ unless node.is_a? MappingNode
2012
+ e = ConfigError.new 'Root configuration must be a mapping'
2013
+
2014
+ e.location = node.start
2015
+ raise e
2016
+ end
2017
+ set_path stream.path if stream.is_a? File
2018
+ @data = wrap_mapping node
2019
+ @cache&.clear
2020
+ end
2021
+
2022
+ def get_from_path(path)
2023
+ @evaluator.refs_seen.clear
2024
+ @evaluator.get_from_path parse_path(path)
2025
+ end
2026
+
2027
+ def convert_string(str)
2028
+ result = @string_converter.call str, self
2029
+ raise ConfigError, "Unable to convert string #{str}" if strict_conversions && result.equal?(str)
2030
+
2031
+ result
2032
+ end
2033
+
2034
+ def evaluated(value, evaluator = nil)
2035
+ result = value
2036
+ if value.is_a? ASTNode
2037
+ e = evaluator.nil? ? @evaluator : evaluator
2038
+ result = e.evaluate value
2039
+ end
2040
+ result
2041
+ end
2042
+
2043
+ MISSING = Object.new
2044
+
2045
+ def base_get(key, default = MISSING)
2046
+ if @cache&.include?(key)
2047
+ result = @cache[key]
2048
+ elsif @data.nil?
2049
+ raise ConfigError, 'No data in configuration'
2050
+ else
2051
+ if @data.include? key
2052
+ result = evaluated @data[key]
2053
+ elsif Config.identifier? key
2054
+ raise ConfigError, "Not found in configuration: #{key}" if default.equal?(MISSING)
2055
+
2056
+ result = default
2057
+ else
2058
+ # not an identifier. Treat as a path
2059
+ begin
2060
+ result = get_from_path key
2061
+ rescue InvalidPathError, BadIndexError, CircularReferenceError
2062
+ raise
2063
+ rescue ConfigError
2064
+ if default.equal? MISSING
2065
+ #e = ConfigError.new "Not found in configuration: #{key}"
2066
+ #raise e
2067
+ raise
2068
+ end
2069
+ result = default
2070
+ end
2071
+ end
2072
+ # if user specified a cache, populate it
2073
+ @cache[key] = result unless @cache.nil?
2074
+ end
2075
+ result
2076
+ end
2077
+
2078
+ def get(key, default = MISSING)
2079
+ unwrap base_get(key, default)
2080
+ end
2081
+
2082
+ def [](key)
2083
+ get key
2084
+ end
2085
+
2086
+ def as_dict
2087
+ @data.nil? ? {} : @data.as_dict
2088
+ end
2089
+ end
2090
+ end