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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +29 -0
- data/README.md +131 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cfg-config.gemspec +28 -0
- data/lib/CFG/config.rb +2090 -0
- data/lib/CFG/version.rb +3 -0
- metadata +55 -0
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
|