hocon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/LICENSE +202 -0
  2. data/README.md +19 -0
  3. data/lib/hocon.rb +2 -0
  4. data/lib/hocon/config_error.rb +12 -0
  5. data/lib/hocon/config_factory.rb +9 -0
  6. data/lib/hocon/config_object.rb +4 -0
  7. data/lib/hocon/config_parse_options.rb +53 -0
  8. data/lib/hocon/config_render_options.rb +46 -0
  9. data/lib/hocon/config_syntax.rb +7 -0
  10. data/lib/hocon/config_value_type.rb +26 -0
  11. data/lib/hocon/impl.rb +5 -0
  12. data/lib/hocon/impl/abstract_config_object.rb +64 -0
  13. data/lib/hocon/impl/abstract_config_value.rb +130 -0
  14. data/lib/hocon/impl/config_concatenation.rb +136 -0
  15. data/lib/hocon/impl/config_float.rb +9 -0
  16. data/lib/hocon/impl/config_impl.rb +10 -0
  17. data/lib/hocon/impl/config_impl_util.rb +78 -0
  18. data/lib/hocon/impl/config_int.rb +31 -0
  19. data/lib/hocon/impl/config_number.rb +27 -0
  20. data/lib/hocon/impl/config_string.rb +37 -0
  21. data/lib/hocon/impl/full_includer.rb +4 -0
  22. data/lib/hocon/impl/origin_type.rb +9 -0
  23. data/lib/hocon/impl/parseable.rb +151 -0
  24. data/lib/hocon/impl/parser.rb +882 -0
  25. data/lib/hocon/impl/path.rb +59 -0
  26. data/lib/hocon/impl/path_builder.rb +36 -0
  27. data/lib/hocon/impl/resolve_status.rb +18 -0
  28. data/lib/hocon/impl/simple_config.rb +11 -0
  29. data/lib/hocon/impl/simple_config_list.rb +70 -0
  30. data/lib/hocon/impl/simple_config_object.rb +178 -0
  31. data/lib/hocon/impl/simple_config_origin.rb +174 -0
  32. data/lib/hocon/impl/simple_include_context.rb +7 -0
  33. data/lib/hocon/impl/simple_includer.rb +19 -0
  34. data/lib/hocon/impl/token.rb +32 -0
  35. data/lib/hocon/impl/token_type.rb +42 -0
  36. data/lib/hocon/impl/tokenizer.rb +370 -0
  37. data/lib/hocon/impl/tokens.rb +157 -0
  38. data/lib/hocon/impl/unmergeable.rb +4 -0
  39. metadata +84 -0
@@ -0,0 +1,7 @@
1
+ require 'hocon/impl'
2
+
3
+ class Hocon::Impl::SimpleIncludeContext
4
+ def initialize(parseable)
5
+ @parseable = parseable
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ require 'hocon/impl'
2
+ require 'hocon/impl/full_includer'
3
+
4
+ class Hocon::Impl::SimpleIncluder < Hocon::Impl::FullIncluder
5
+ class Proxy < Hocon::Impl::FullIncluder
6
+ def initialize(delegate)
7
+ @delegate = delegate
8
+ end
9
+ ## TODO: port remaining implementation when needed
10
+ end
11
+
12
+ def self.make_full(includer)
13
+ if includer.is_a?(Hocon::Impl::FullIncluder)
14
+ includer
15
+ else
16
+ Proxy.new(includer)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ require 'hocon/impl'
2
+ require 'hocon/impl/token_type'
3
+
4
+ class Hocon::Impl::Token
5
+ def self.new_without_origin(token_type, debug_string)
6
+ Hocon::Impl::Token.new(token_type, nil, debug_string)
7
+ end
8
+
9
+ def initialize(token_type, origin, debug_string = nil)
10
+ @token_type = token_type
11
+ @origin = origin
12
+ @debug_string = debug_string
13
+ end
14
+
15
+ attr_reader :origin
16
+
17
+ def line_number
18
+ if @origin
19
+ @origin.line_number
20
+ else
21
+ -1
22
+ end
23
+ end
24
+
25
+ def to_s
26
+ if !@debug_string.nil?
27
+ @debug_string
28
+ else
29
+ Hocon::Impl::TokenType.name(@token_type)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ require 'hocon/impl'
2
+
3
+ class Hocon::Impl::TokenType
4
+ START = 0
5
+ EOF = 1
6
+ COMMA = 2
7
+ EQUALS = 3
8
+ COLON = 4
9
+ OPEN_CURLY = 5
10
+ CLOSE_CURLY = 6
11
+ OPEN_SQUARE = 7
12
+ CLOSE_SQUARE = 8
13
+ VALUE = 9
14
+ NEWLINE = 10
15
+ UNQUOTED_TEXT = 11
16
+ SUBSTITUTION = 12
17
+ PROBLEM = 13
18
+ COMMENT = 14
19
+ PLUS_EQUALS = 15
20
+
21
+ def self.name(token_type)
22
+ case token_type
23
+ when START then "START"
24
+ when EOF then "EOF"
25
+ when COMMA then "COMMA"
26
+ when EQUALS then "EQUALS"
27
+ when COLON then "COLON"
28
+ when OPEN_CURLY then "OPEN_CURLY"
29
+ when CLOSE_CURLY then "CLOSE_CURLY"
30
+ when OPEN_SQUARE then "OPEN_SQUARE"
31
+ when CLOSE_SQUARE then "CLOSE_SQUARE"
32
+ when VALUE then "VALUE"
33
+ when NEWLINE then "NEWLINE"
34
+ when UNQUOTED_TEXT then "UNQUOTED_TEXT"
35
+ when SUBSTITUTION then "SUBSTITUTION"
36
+ when PROBLEM then "PROBLEM"
37
+ when COMMENT then "COMMENT"
38
+ when PLUS_EQUALS then "PLUS_EQUALS"
39
+ else raise ConfigBugError, "Unrecognized token type #{token_type}"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,370 @@
1
+ require 'hocon/impl'
2
+ require 'hocon/impl/config_impl_util'
3
+ require 'hocon/impl/tokens'
4
+ require 'stringio'
5
+
6
+ class Hocon::Impl::Tokenizer
7
+ Tokens = Hocon::Impl::Tokens
8
+
9
+ class TokenizerProblemError < StandardError
10
+ end
11
+
12
+ class TokenIterator
13
+ class WhitespaceSaver
14
+ def initialize
15
+ @whitespace = StringIO.new
16
+ @last_token_was_simple_value = false
17
+ end
18
+
19
+ def add(c)
20
+ if @last_token_was_simple_value
21
+ @whitespace << c
22
+ end
23
+ end
24
+
25
+ def check(t, base_origin, line_number)
26
+ if Hocon::Impl::Tokenizer::TokenIterator.simple_value?(t)
27
+ next_is_a_simple_value(base_origin, line_number)
28
+ else
29
+ next_is_not_a_simple_value
30
+ nil
31
+ end
32
+ end
33
+
34
+ private
35
+ # called if the next token is not a simple value;
36
+ # discards any whitespace we were saving between
37
+ # simple values.
38
+ def next_is_not_a_simple_value
39
+ @last_token_was_simple_value = false
40
+ @whitespace.reopen("")
41
+ end
42
+
43
+ # called if the next token IS a simple value,
44
+ # so creates a whitespace token if the previous
45
+ # token also was.
46
+ def next_is_a_simple_value(base_origin, line_number)
47
+ if @last_token_was_simple_value
48
+ # need to save whitespace between the two so
49
+ # the parser has the option to concatenate it.
50
+ if @whitespace.length > 0
51
+ Tokens.new_unquoted_text(
52
+ line_origin(base_origin, line_number),
53
+ @whitespace.string = ""
54
+ )
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ # chars JSON allows a number to start with
62
+ FIRST_NUMBER_CHARS = "0123456789-"
63
+ # chars JSON allows to be part of a number
64
+ NUMBER_CHARS = "0123456789eE+-."
65
+ # chars that stop an unquoted string
66
+ NOT_IN_UNQUOTED_TEXT = "$\"{}[]:=,+#`^?!@*&\\"
67
+
68
+ def self.simple_value?(t)
69
+ Tokens.substitution?(t) ||
70
+ Tokens.unquoted_text?(t) ||
71
+ Tokens.value?(t)
72
+ end
73
+
74
+ def self.whitespace?(c)
75
+ Hocon::Impl::ConfigImplUtil.whitespace?(c)
76
+ end
77
+
78
+ def self.whitespace_not_newline?(c)
79
+ (c != "\n") and (Hocon::Impl::ConfigImplUtil.whitespace?(c))
80
+ end
81
+
82
+ def initialize(origin, input, allow_comments)
83
+ @origin = origin
84
+ @input = input
85
+ @allow_comments = allow_comments
86
+ @buffer = []
87
+ @line_number = 1
88
+ @line_origin = @origin.set_line_number(@line_number)
89
+ @tokens = []
90
+ @tokens << Tokens::START
91
+ @whitespace_saver = WhitespaceSaver.new
92
+ end
93
+
94
+ def start_of_comment?(c)
95
+ if c == -1
96
+ false
97
+ else
98
+ if @allow_comments
99
+ if c == '#'
100
+ true
101
+ elsif c == '/'
102
+ maybe_second_slash = next_char_raw
103
+ # we want to predictably NOT consume any chars
104
+ put_back(maybe_second_slash)
105
+ if maybe_second_slash == '/'
106
+ true
107
+ else
108
+ false
109
+ end
110
+ end
111
+ else
112
+ false
113
+ end
114
+ end
115
+ end
116
+
117
+ def put_back(c)
118
+ if @buffer.length > 2
119
+ raise ConfigBugError, "bug: putBack() three times, undesirable look-ahead"
120
+ end
121
+ @buffer.push(c)
122
+ end
123
+
124
+ def next_char_raw
125
+ if @buffer.empty?
126
+ begin
127
+ @input.readchar
128
+ rescue EOFError
129
+ -1
130
+ end
131
+ else
132
+ @buffer.pop
133
+ end
134
+ end
135
+
136
+ def next_char_after_whitespace(saver)
137
+ while true
138
+ c = next_char_raw
139
+ if c == -1
140
+ return -1
141
+ else
142
+ if self.class.whitespace_not_newline?(c)
143
+ saver.add(c)
144
+ else
145
+ return c
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # The rules here are intended to maximize convenience while
152
+ # avoiding confusion with real valid JSON. Basically anything
153
+ # that parses as JSON is treated the JSON way and otherwise
154
+ # we assume it's a string and let the parser sort it out.
155
+ def pull_unquoted_text
156
+ origin = @line_origin
157
+ io = StringIO.new
158
+ c = next_char_raw
159
+ while true
160
+ if (c == -1) or
161
+ (NOT_IN_UNQUOTED_TEXT.index(c)) or
162
+ (self.class.whitespace?(c)) or
163
+ (start_of_comment?(c))
164
+ break
165
+ else
166
+ io << c
167
+ end
168
+
169
+ # we parse true/false/null tokens as such no matter
170
+ # what is after them, as long as they are at the
171
+ # start of the unquoted token.
172
+ if io.length == 4
173
+ if io.string == "true"
174
+ return Tokens.new_boolean(origin, true)
175
+ elsif io.string == "null"
176
+ return Tokens.new_null(origin)
177
+ end
178
+ elsif io.length == 5
179
+ if io.string == "false"
180
+ return Tokens.new_boolean(origin, false)
181
+ end
182
+ end
183
+
184
+ c = next_char_raw
185
+ end
186
+
187
+ # put back the char that ended the unquoted text
188
+ put_back(c)
189
+
190
+ Tokens.new_unquoted_text(origin, io.string)
191
+ end
192
+
193
+
194
+ def pull_comment(first_char)
195
+ if first_char == '/'
196
+ discard = next_char_raw
197
+ if discard != '/'
198
+ raise ConfigBugError, "called pullComment but // not seen"
199
+ end
200
+ end
201
+
202
+ io = StringIO.new
203
+ while true
204
+ c = next_char_raw
205
+ if (c == -1) || (c == "\n")
206
+ put_back(c)
207
+ return Tokens.new_comment(@line_origin, io.string)
208
+ else
209
+ io << c
210
+ end
211
+ end
212
+ end
213
+
214
+ def pull_number(first_char)
215
+ sb = StringIO.new
216
+ sb << first_char
217
+ contained_decimal_or_e = false
218
+ c = next_char_raw
219
+ while (c != -1) && (NUMBER_CHARS.index(c))
220
+ if (c == '.') ||
221
+ (c == 'e') ||
222
+ (c == 'E')
223
+ contained_decimal_or_e = true
224
+ end
225
+ sb << c
226
+ c = next_char_raw
227
+ end
228
+ # the last character we looked at wasn't part of the number, put it
229
+ # back
230
+ put_back(c)
231
+ s = sb.string
232
+ begin
233
+ if contained_decimal_or_e
234
+ # force floating point representation
235
+ Tokens.new_double(@line_origin, s.to_f, s)
236
+ else
237
+ Tokens.new_long(@line_origin, s.to_i, s)
238
+ end
239
+ rescue ArgumentError => e
240
+ if e.message =~ /^invalid value for (Float|Integer)\(\)/
241
+ # not a number after all, see if it's an unquoted string.
242
+ s.each do |u|
243
+ if NOT_IN_UNQUOTED_TEXT.index
244
+ raise problem(u, "Reserved character '#{u}'" +
245
+ "is not allowed outside quotes", true)
246
+ end
247
+ end
248
+ # no evil chars so we just decide this was a string and
249
+ # not a number.
250
+ Tokens.new_unquoted_text(@line_origin, s)
251
+ else
252
+ raise e
253
+ end
254
+ end
255
+ end
256
+
257
+ def pull_quoted_string
258
+ # the open quote has already been consumed
259
+ sb = StringIO.new
260
+ c = ""
261
+ while c != '"'
262
+ c = next_char_raw
263
+ if c == -1
264
+ raise problem("End of input but string quote was still open")
265
+ end
266
+
267
+ if c == "\\"
268
+ pull_escape_sequence(sb)
269
+ elsif c == '"'
270
+ # done!
271
+ elsif c =~ /[[:cntrl:]]/
272
+ raise problem(c, "JSON does not allow unescaped #{c}" +
273
+ " in quoted strings, use a backslash escape")
274
+ else
275
+ sb << c
276
+ end
277
+ end
278
+
279
+ # maybe switch to triple-quoted string, sort of hacky...
280
+ if sb.length == 0
281
+ third = next_char_raw
282
+ if third == '"'
283
+ append_triple_quoted_string(sb)
284
+ else
285
+ put_back(third)
286
+ end
287
+ end
288
+
289
+ Tokens.new_string(@line_origin, sb.string)
290
+ end
291
+
292
+ def pull_next_token(saver)
293
+ c = next_char_after_whitespace(saver)
294
+ if c == -1
295
+ Tokens::EOF
296
+ elsif c == "\n"
297
+ # newline tokens have the just-ended line number
298
+ line = Tokens.new_line(@line_origin)
299
+ @line_number += 1
300
+ @line_origin = @origin.set_line_number(@line_number)
301
+ line
302
+ else
303
+ t = nil
304
+ if start_of_comment?(c)
305
+ t = pull_comment(c)
306
+ else
307
+ t = case c
308
+ when '"' then pull_quoted_string
309
+ when '$' then pull_substitution
310
+ when ':' then Tokens::COLON
311
+ when ',' then Tokens::COMMA
312
+ when '=' then Tokens::EQUALS
313
+ when '{' then Tokens::OPEN_CURLY
314
+ when '}' then Tokens::CLOSE_CURLY
315
+ when '[' then Tokens::OPEN_SQUARE
316
+ when ']' then Tokens::CLOSE_SQUARE
317
+ when '+' then pull_plus_equals
318
+ else nil
319
+ end
320
+
321
+ if t.nil?
322
+ if FIRST_NUMBER_CHARS.index(c)
323
+ t = pull_number(c)
324
+ elsif NOT_IN_UNQUOTED_TEXT.index(c)
325
+ raise problem(c, "Reserved character '#{c}' is not allowed outside quotes", true)
326
+ else
327
+ put_back(c)
328
+ t = pull_unquoted_text
329
+ end
330
+ end
331
+ end
332
+
333
+ if t.nil?
334
+ raise ConfigBugError, "bug: failed to generate next token"
335
+ end
336
+
337
+ t
338
+ end
339
+ end
340
+
341
+ def queue_next_token
342
+ t = pull_next_token(@whitespace_saver)
343
+ whitespace = @whitespace_saver.check(t, @origin, @line_number)
344
+ if whitespace
345
+ @tokens.push(whitespace)
346
+ end
347
+ @tokens.push(t)
348
+ end
349
+
350
+ def next
351
+ t = @tokens.shift
352
+ if (@tokens.empty?) and (t != Tokens::EOF)
353
+ begin
354
+ queue_next_token
355
+ rescue TokenizerProblemError => e
356
+ @tokens.push(e.problem)
357
+ end
358
+ if @tokens.empty?
359
+ raise ConfigBugError, "bug: tokens queue should not be empty here"
360
+ end
361
+ end
362
+ t
363
+ end
364
+ end
365
+
366
+
367
+ def self.tokenize(origin, input, syntax)
368
+ TokenIterator.new(origin, input, syntax != Hocon::ConfigSyntax::JSON)
369
+ end
370
+ end