cfg-config 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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