hocon 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +4 -2
  3. data/lib/hocon.rb +2 -0
  4. data/lib/hocon/config.rb +1010 -0
  5. data/lib/hocon/config_error.rb +32 -2
  6. data/lib/hocon/config_factory.rb +46 -0
  7. data/lib/hocon/config_include_context.rb +49 -0
  8. data/lib/hocon/config_includer_file.rb +27 -0
  9. data/lib/hocon/config_list.rb +49 -0
  10. data/lib/hocon/config_mergeable.rb +74 -0
  11. data/lib/hocon/config_object.rb +144 -1
  12. data/lib/hocon/config_parse_options.rb +33 -9
  13. data/lib/hocon/config_parseable.rb +51 -0
  14. data/lib/hocon/config_render_options.rb +4 -2
  15. data/lib/hocon/config_resolve_options.rb +31 -0
  16. data/lib/hocon/config_syntax.rb +5 -2
  17. data/lib/hocon/config_util.rb +73 -0
  18. data/lib/hocon/config_value.rb +122 -0
  19. data/lib/hocon/config_value_factory.rb +66 -2
  20. data/lib/hocon/config_value_type.rb +5 -2
  21. data/lib/hocon/impl.rb +2 -0
  22. data/lib/hocon/impl/abstract_config_node.rb +29 -0
  23. data/lib/hocon/impl/abstract_config_node_value.rb +11 -0
  24. data/lib/hocon/impl/abstract_config_object.rb +148 -42
  25. data/lib/hocon/impl/abstract_config_value.rb +251 -11
  26. data/lib/hocon/impl/array_iterator.rb +19 -0
  27. data/lib/hocon/impl/config_boolean.rb +7 -1
  28. data/lib/hocon/impl/config_concatenation.rb +177 -28
  29. data/lib/hocon/impl/config_delayed_merge.rb +329 -0
  30. data/lib/hocon/impl/config_delayed_merge_object.rb +274 -0
  31. data/lib/hocon/impl/config_document_parser.rb +647 -0
  32. data/lib/hocon/impl/config_double.rb +44 -0
  33. data/lib/hocon/impl/config_impl.rb +143 -19
  34. data/lib/hocon/impl/config_impl_util.rb +18 -0
  35. data/lib/hocon/impl/config_include_kind.rb +10 -0
  36. data/lib/hocon/impl/config_int.rb +13 -1
  37. data/lib/hocon/impl/config_node_array.rb +11 -0
  38. data/lib/hocon/impl/config_node_comment.rb +19 -0
  39. data/lib/hocon/impl/config_node_complex_value.rb +54 -0
  40. data/lib/hocon/impl/config_node_concatenation.rb +11 -0
  41. data/lib/hocon/impl/config_node_field.rb +81 -0
  42. data/lib/hocon/impl/config_node_include.rb +33 -0
  43. data/lib/hocon/impl/config_node_object.rb +276 -0
  44. data/lib/hocon/impl/config_node_path.rb +48 -0
  45. data/lib/hocon/impl/config_node_root.rb +60 -0
  46. data/lib/hocon/impl/config_node_simple_value.rb +42 -0
  47. data/lib/hocon/impl/config_node_single_token.rb +17 -0
  48. data/lib/hocon/impl/config_null.rb +15 -7
  49. data/lib/hocon/impl/config_number.rb +43 -4
  50. data/lib/hocon/impl/config_parser.rb +403 -0
  51. data/lib/hocon/impl/config_reference.rb +142 -0
  52. data/lib/hocon/impl/config_string.rb +55 -7
  53. data/lib/hocon/impl/container.rb +29 -0
  54. data/lib/hocon/impl/default_transformer.rb +24 -15
  55. data/lib/hocon/impl/from_map_mode.rb +3 -1
  56. data/lib/hocon/impl/full_includer.rb +2 -0
  57. data/lib/hocon/impl/memo_key.rb +42 -0
  58. data/lib/hocon/impl/mergeable_value.rb +8 -0
  59. data/lib/hocon/impl/origin_type.rb +8 -2
  60. data/lib/hocon/impl/parseable.rb +455 -91
  61. data/lib/hocon/impl/path.rb +181 -59
  62. data/lib/hocon/impl/path_builder.rb +24 -3
  63. data/lib/hocon/impl/path_parser.rb +280 -0
  64. data/lib/hocon/impl/replaceable_merge_stack.rb +22 -0
  65. data/lib/hocon/impl/resolve_context.rb +254 -0
  66. data/lib/hocon/impl/resolve_memos.rb +21 -0
  67. data/lib/hocon/impl/resolve_result.rb +39 -0
  68. data/lib/hocon/impl/resolve_source.rb +354 -0
  69. data/lib/hocon/impl/resolve_status.rb +3 -1
  70. data/lib/hocon/impl/simple_config.rb +264 -10
  71. data/lib/hocon/impl/simple_config_document.rb +48 -0
  72. data/lib/hocon/impl/simple_config_list.rb +282 -8
  73. data/lib/hocon/impl/simple_config_object.rb +424 -88
  74. data/lib/hocon/impl/simple_config_origin.rb +263 -71
  75. data/lib/hocon/impl/simple_include_context.rb +31 -1
  76. data/lib/hocon/impl/simple_includer.rb +196 -1
  77. data/lib/hocon/impl/substitution_expression.rb +38 -0
  78. data/lib/hocon/impl/token.rb +17 -4
  79. data/lib/hocon/impl/token_type.rb +6 -2
  80. data/lib/hocon/impl/tokenizer.rb +339 -109
  81. data/lib/hocon/impl/tokens.rb +330 -79
  82. data/lib/hocon/impl/unmergeable.rb +14 -1
  83. data/lib/hocon/impl/unsupported_operation_error.rb +6 -0
  84. data/lib/hocon/impl/url.rb +37 -0
  85. data/lib/hocon/parser.rb +7 -0
  86. data/lib/hocon/parser/config_document.rb +92 -0
  87. data/lib/hocon/parser/config_document_factory.rb +36 -0
  88. data/lib/hocon/parser/config_node.rb +30 -0
  89. metadata +67 -43
  90. data/lib/hocon/impl/config_float.rb +0 -13
  91. data/lib/hocon/impl/parser.rb +0 -977
  92. data/lib/hocon/impl/properties_parser.rb +0 -83
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon/impl'
4
+ require 'hocon/impl/config_node_array'
5
+ require 'hocon/impl/config_node_complex_value'
6
+ require 'hocon/impl/config_node_object'
7
+
8
+ class Hocon::Impl::ConfigNodeRoot
9
+ include Hocon::Impl::ConfigNodeComplexValue
10
+ def initialize(children, origin)
11
+ super(children)
12
+ @origin = origin
13
+ end
14
+
15
+ def new_node(nodes)
16
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "Tried to indent the root object"
17
+ end
18
+
19
+ def value
20
+ @children.each do |node|
21
+ if node.is_a?(Hocon::Impl::ConfigNodeComplexValue)
22
+ return node
23
+ end
24
+ end
25
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value"
26
+ end
27
+
28
+ def set_value(desired_path, value, flavor)
29
+ children_copy = @children.clone
30
+ children_copy.each_with_index do |node, index|
31
+ if node.is_a?(Hocon::Impl::ConfigNodeComplexValue)
32
+ if node.is_a?(Hocon::Impl::ConfigNodeArray)
33
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."
34
+ elsif node.is_a?(Hocon::Impl::ConfigNodeObject)
35
+ if value.nil?
36
+ children_copy[index] = node.remove_value_on_path(desired_path, flavor)
37
+ else
38
+ children_copy[index] = node.set_value_on_path(desired_path, value, flavor)
39
+ end
40
+ return self.class.new(children_copy, @origin)
41
+ end
42
+ end
43
+ end
44
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value"
45
+ end
46
+
47
+ def has_value(desired_path)
48
+ path = Hocon::Impl::PathParser.parse_path(desired_path)
49
+ @children.each do |node|
50
+ if node.is_a?(Hocon::Impl::ConfigNodeComplexValue)
51
+ if node.is_a?(Hocon::Impl::ConfigNodeArray)
52
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."
53
+ elsif node.is_a?(Hocon::Impl::ConfigNodeObject)
54
+ return node.has_value(path)
55
+ end
56
+ end
57
+ end
58
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value"
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon/config_error'
4
+ require 'hocon/impl'
5
+ require 'hocon/impl/abstract_config_node_value'
6
+ require 'hocon/impl/array_iterator'
7
+ require 'hocon/impl/config_reference'
8
+ require 'hocon/impl/config_string'
9
+ require 'hocon/impl/path_parser'
10
+ require 'hocon/impl/substitution_expression'
11
+ require 'hocon/impl/tokens'
12
+
13
+ class Hocon::Impl::ConfigNodeSimpleValue
14
+ include Hocon::Impl::AbstractConfigNodeValue
15
+
16
+ Tokens = Hocon::Impl::Tokens
17
+
18
+ def initialize(value)
19
+ @token = value
20
+ end
21
+
22
+ attr_reader :token
23
+
24
+ def tokens
25
+ [@token]
26
+ end
27
+
28
+ def value
29
+ if Tokens.value?(@token)
30
+ return Tokens.value(@token)
31
+ elsif Tokens.unquoted_text?(@token)
32
+ return Hocon::Impl::ConfigString::Unquoted.new(@token.origin, Tokens.unquoted_text(@token))
33
+ elsif Tokens.substitution?(@token)
34
+ expression = Tokens.get_substitution_path_expression(@token)
35
+ path = Hocon::Impl::PathParser.parse_path_expression(Hocon::Impl::ArrayIterator.new(expression), @token.origin)
36
+ optional = Tokens.get_substitution_optional(@token)
37
+
38
+ return Hocon::Impl::ConfigReference.new(@token.origin, Hocon::Impl::SubstitutionExpression.new(path, optional))
39
+ end
40
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, 'ConfigNodeSimpleValue did not contain a valid value token'
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon/impl'
4
+ require 'hocon/impl/abstract_config_node'
5
+
6
+ class Hocon::Impl::ConfigNodeSingleToken
7
+ include Hocon::Impl::AbstractConfigNode
8
+ def initialize(t)
9
+ @token = t
10
+ end
11
+
12
+ attr_reader :token
13
+
14
+ def tokens
15
+ [@token]
16
+ end
17
+ end
@@ -1,9 +1,17 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon/impl'
2
4
  require 'hocon/config_value_type'
3
5
 
4
- class Hocon::Impl::ConfigNull < Hocon::Impl::AbstractConfigValue
6
+ class Hocon::Impl::ConfigNull
7
+ include Hocon::Impl::AbstractConfigValue
8
+
9
+ def initialize(origin)
10
+ super(origin)
11
+ end
12
+
5
13
  def value_type
6
- Hocon::Impl::ConfigValueType::NULL
14
+ Hocon::ConfigValueType::NULL
7
15
  end
8
16
 
9
17
  def unwrapped
@@ -14,12 +22,12 @@ class Hocon::Impl::ConfigNull < Hocon::Impl::AbstractConfigValue
14
22
  "null"
15
23
  end
16
24
 
17
- def render(sb, indent, atRoot, options)
18
- sb.append("null")
25
+ def render_value_to_sb(sb, indent, at_root, options)
26
+ sb << "null"
19
27
  end
20
28
 
21
- def newCopy(origin)
22
- ConfigNull.new(origin)
29
+ def new_copy(origin)
30
+ self.class.new(origin)
23
31
  end
24
32
 
25
- end
33
+ end
@@ -1,18 +1,21 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon/impl'
2
4
  require 'hocon/impl/abstract_config_value'
3
5
 
4
- class Hocon::Impl::ConfigNumber < Hocon::Impl::AbstractConfigValue
6
+ class Hocon::Impl::ConfigNumber
7
+ include Hocon::Impl::AbstractConfigValue
5
8
  ## sigh... requiring these subclasses before this class
6
9
  ## is declared would cause an error. Thanks, ruby.
7
10
  require 'hocon/impl/config_int'
8
- require 'hocon/impl/config_float'
11
+ require 'hocon/impl/config_double'
9
12
 
10
13
  def self.new_number(origin, number, original_text)
11
14
  as_int = number.to_i
12
15
  if as_int == number
13
16
  Hocon::Impl::ConfigInt.new(origin, as_int, original_text)
14
17
  else
15
- Hocon::Impl::ConfigFloat.new(origin, number, original_text)
18
+ Hocon::Impl::ConfigDouble.new(origin, number, original_text)
16
19
  end
17
20
  end
18
21
 
@@ -20,8 +23,44 @@ class Hocon::Impl::ConfigNumber < Hocon::Impl::AbstractConfigValue
20
23
  super(origin)
21
24
  @original_text = original_text
22
25
  end
26
+ attr_reader :original_text
23
27
 
24
28
  def transform_to_string
25
29
  @original_text
26
30
  end
27
- end
31
+
32
+ def int_value_range_checked(path)
33
+ # We don't need to do any range checking here due to the way Ruby handles
34
+ # integers (doesn't have the 32-bit/64-bit distinction that Java does).
35
+ long_value
36
+ end
37
+
38
+ def long_value
39
+ raise "long_value needs to be overriden by sub-classes of #{Hocon::Impl::ConfigNumber}, in this case #{self.class}"
40
+ end
41
+
42
+ def can_equal(other)
43
+ other.is_a?(Hocon::Impl::ConfigNumber)
44
+ end
45
+
46
+ def ==(other)
47
+ if other.is_a?(Hocon::Impl::ConfigNumber) && can_equal(other)
48
+ @value == other.value
49
+ else
50
+ false
51
+ end
52
+ end
53
+
54
+ def hash
55
+ # This hash function makes it so that a ConfigNumber with a 3.0
56
+ # and one with a 3 will return the hash code
57
+ to_int = @value.round
58
+
59
+ # If the value is an integer or a floating point equal to an integer
60
+ if to_int == @value
61
+ to_int.hash
62
+ else
63
+ @value.hash
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,403 @@
1
+ # encoding: utf-8
2
+
3
+ require 'stringio'
4
+ require 'hocon/impl'
5
+ require 'hocon/impl/path_builder'
6
+ require 'hocon/config_syntax'
7
+ require 'hocon/impl/config_string'
8
+ require 'hocon/impl/config_concatenation'
9
+ require 'hocon/config_error'
10
+ require 'hocon/impl/simple_config_list'
11
+ require 'hocon/impl/simple_config_object'
12
+ require 'hocon/impl/path'
13
+ require 'hocon/impl/url'
14
+ require 'hocon/impl/config_reference'
15
+ require 'hocon/impl/substitution_expression'
16
+ require 'hocon/impl/config_node_simple_value'
17
+ require 'hocon/impl/config_node_object'
18
+ require 'hocon/impl/config_node_array'
19
+ require 'hocon/impl/config_node_concatenation'
20
+ require 'hocon/impl/config_include_kind'
21
+
22
+ class Hocon::Impl::ConfigParser
23
+
24
+ ConfigSyntax = Hocon::ConfigSyntax
25
+ ConfigConcatenation = Hocon::Impl::ConfigConcatenation
26
+ ConfigReference = Hocon::Impl::ConfigReference
27
+ ConfigParseError = Hocon::ConfigError::ConfigParseError
28
+ ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
29
+ SimpleConfigObject = Hocon::Impl::SimpleConfigObject
30
+ SimpleConfigList = Hocon::Impl::SimpleConfigList
31
+ Path = Hocon::Impl::Path
32
+ ConfigIncludeKind = Hocon::Impl::ConfigIncludeKind
33
+ ConfigNodeInclude = Hocon::Impl::ConfigNodeInclude
34
+ ConfigNodeComment = Hocon::Impl::ConfigNodeComment
35
+ ConfigNodeSingleToken = Hocon::Impl::ConfigNodeSingleToken
36
+ Tokens = Hocon::Impl::Tokens
37
+
38
+ def self.parse(document, origin, options, include_context)
39
+ context = Hocon::Impl::ConfigParser::ParseContext.new(
40
+ options.syntax, origin, document,
41
+ Hocon::Impl::SimpleIncluder.make_full(options.includer),
42
+ include_context)
43
+ context.parse
44
+ end
45
+
46
+ class ParseContext
47
+ def initialize(flavor, origin, document, includer, include_context)
48
+ @line_number = 1
49
+ @document = document
50
+ @flavor = flavor
51
+ @base_origin = origin
52
+ @includer = includer
53
+ @include_context = include_context
54
+ @path_stack = []
55
+ @array_count = 0
56
+ end
57
+
58
+ # merge a bunch of adjacent values into one
59
+ # value; change unquoted text into a string
60
+ # value.
61
+ def parse_concatenation(n)
62
+ # this trick is not done in JSON
63
+ if @flavor.equal?(ConfigSyntax::JSON)
64
+ raise ConfigBugOrBrokenError, "Found a concatenation node in JSON"
65
+ end
66
+
67
+ values = []
68
+
69
+ n.children.each do |node|
70
+ if node.is_a?(Hocon::Impl::AbstractConfigNodeValue)
71
+ v = parse_value(node, nil)
72
+ values.push(v)
73
+ end
74
+ end
75
+
76
+ ConfigConcatenation.concatenate(values)
77
+ end
78
+
79
+ def line_origin
80
+ @base_origin.with_line_number(@line_number)
81
+ end
82
+
83
+ def parse_error(message, cause = nil)
84
+ ConfigParseError.new(line_origin, message, cause)
85
+ end
86
+
87
+ def full_current_path
88
+ # pathStack has top of stack at front
89
+ if @path_stack.empty?
90
+ raise ConfigBugOrBrokenError, "Bug in parser; tried to get current path when at root"
91
+ else
92
+ Path.from_path_list(@path_stack.reverse)
93
+ end
94
+ end
95
+
96
+ def parse_value(n, comments)
97
+ starting_array_count = @array_count
98
+
99
+ if n.is_a?(Hocon::Impl::ConfigNodeSimpleValue)
100
+ v = n.value
101
+ elsif n.is_a?(Hocon::Impl::ConfigNodeObject)
102
+ v = parse_object(n)
103
+ elsif n.is_a?(Hocon::Impl::ConfigNodeArray)
104
+ v = parse_array(n)
105
+ elsif n.is_a?(Hocon::Impl::ConfigNodeConcatenation)
106
+ v = parse_concatenation(n)
107
+ else
108
+ raise parse_error("Expecting a value but got wrong node type: #{n.class}")
109
+ end
110
+
111
+ unless comments.nil? || comments.empty?
112
+ v = v.with_origin(v.origin.prepend_comments(comments.clone))
113
+ comments.clear
114
+ end
115
+
116
+ unless @array_count == starting_array_count
117
+ raise ConfigBugOrBrokenError, "Bug in config parser: unbalanced array count"
118
+ end
119
+
120
+ v
121
+ end
122
+
123
+ def create_value_under_path(path, value)
124
+ # for path foo.bar, we are creating
125
+ # { "foo" : { "bar" : value } }
126
+ keys = []
127
+
128
+ key = path.first
129
+ remaining = path.remainder
130
+ until key.nil?
131
+ keys.push(key)
132
+ if remaining.nil?
133
+ break
134
+ else
135
+ key = remaining.first
136
+ remaining = remaining.remainder
137
+ end
138
+ end
139
+
140
+ # the setComments(null) is to ensure comments are only
141
+ # on the exact leaf node they apply to.
142
+ # a comment before "foo.bar" applies to the full setting
143
+ # "foo.bar" not also to "foo"
144
+ keys = keys.reverse
145
+ # this is just a ruby means for doing first/rest
146
+ deepest, *rest = *keys
147
+ o = SimpleConfigObject.new(value.origin.with_comments(nil),
148
+ {deepest => value})
149
+ while !rest.empty?
150
+ deepest, *rest = *rest
151
+ o = SimpleConfigObject.new(value.origin.with_comments(nil),
152
+ {deepest => o})
153
+ end
154
+
155
+ o
156
+ end
157
+
158
+ def parse_include(values, n)
159
+ case n.kind
160
+ when ConfigIncludeKind::URL
161
+ url = nil
162
+ begin
163
+ url = Hocon::Impl::Url.new(n.name)
164
+ rescue Hocon::Impl::Url::MalformedUrlError => e
165
+ raise parse_error("include url() specifies an invalid URL: #{n.name}", e)
166
+ end
167
+ obj = @includer.include_url(@include_context, url)
168
+ when ConfigIncludeKind::FILE
169
+ obj = @includer.include_file(@include_context, n.name)
170
+ when ConfigIncludeKind::CLASSPATH
171
+ obj = @includer.include_resources(@include_context, n.name)
172
+ when ConfigIncludeKind::HEURISTIC
173
+ obj = @includer.include(@include_context, n.name)
174
+ else
175
+ raise ConfigBugOrBrokenError, "should not be reached"
176
+ end
177
+
178
+ # we really should make this work, but for now throwing an
179
+ # exception is better than producing an incorrect result.
180
+ # See https://github.com/typesafehub/config/issues/160
181
+ if @array_count > 0 && (obj.resolve_status != Hocon::Impl::ResolveStatus::RESOLVED)
182
+ raise parse_error("Due to current limitations of the config parser, when an include statement is nested inside a list value, " +
183
+ "${} substitutions inside the included file cannot be resolved correctly. Either move the include outside of the list value or " +
184
+ "remove the ${} statements from the included file.")
185
+ end
186
+
187
+ if !(@path_stack.empty?)
188
+ prefix = full_current_path
189
+ obj = obj.relativized(prefix)
190
+ end
191
+
192
+ obj.key_set.each do |key|
193
+ v = obj.get(key)
194
+ existing = values[key]
195
+ if !(existing.nil?)
196
+ values[key] = v.with_fallback(existing)
197
+ else
198
+ values[key] = v
199
+ end
200
+ end
201
+ end
202
+
203
+ def parse_object(n)
204
+ values = Hash.new
205
+ object_origin = line_origin
206
+ last_was_new_line = false
207
+
208
+ nodes = n.children.clone
209
+ comments = []
210
+ i = 0
211
+ while i < nodes.size
212
+ node = nodes[i]
213
+ if node.is_a?(ConfigNodeComment)
214
+ last_was_new_line = false
215
+ comments.push(node.comment_text)
216
+ elsif node.is_a?(ConfigNodeSingleToken) && Tokens.newline?(node.token)
217
+ @line_number += 1
218
+ if last_was_new_line
219
+ # Drop all comments if there was a blank line and start a new comment block
220
+ comments.clear
221
+ end
222
+ last_was_new_line = true
223
+ elsif !@flavor.equal?(ConfigSyntax::JSON) && node.is_a?(ConfigNodeInclude)
224
+ parse_include(values, node)
225
+ last_was_new_line = false
226
+ elsif node.is_a?(Hocon::Impl::ConfigNodeField)
227
+ last_was_new_line = false
228
+ path = node.path.value
229
+ comments += node.comments
230
+
231
+ # path must be on-stack while we parse the value
232
+ # Note that, in upstream, pathStack is a LinkedList, so use unshift instead of push
233
+ @path_stack.unshift(path)
234
+ if node.separator.equal?(Tokens::PLUS_EQUALS)
235
+ # we really should make this work, but for now throwing
236
+ # an exception is better than producing an incorrect
237
+ # result. See
238
+ # https://github.com/typesafehub/config/issues/160
239
+ if @array_count > 0
240
+ raise parse_error("Due to current limitations of the config parser, += does not work nested inside a list. " +
241
+ "+= expands to a ${} substitution and the path in ${} cannot currently refer to list elements. " +
242
+ "You might be able to move the += outside of the list and then refer to it from inside the list with ${}.")
243
+ end
244
+
245
+ # because we will put it in an array after the fact so
246
+ # we want this to be incremented during the parseValue
247
+ # below in order to throw the above exception.
248
+ @array_count += 1
249
+ end
250
+
251
+ value_node = node.value
252
+
253
+ # comments from the key token go to the value token
254
+ new_value = parse_value(value_node, comments)
255
+
256
+ if node.separator.equal?(Tokens::PLUS_EQUALS)
257
+ @array_count -= 1
258
+
259
+ concat = []
260
+ previous_ref = ConfigReference.new(new_value.origin,
261
+ Hocon::Impl::SubstitutionExpression.new(full_current_path, true))
262
+ list = SimpleConfigList.new(new_value.origin, [new_value])
263
+ concat << previous_ref
264
+ concat << list
265
+ new_value = ConfigConcatenation.concatenate(concat)
266
+ end
267
+
268
+ # Grab any trailing comments on the same line
269
+ if i < nodes.size - 1
270
+ i += 1
271
+ while i < nodes.size
272
+ if nodes[i].is_a?(ConfigNodeComment)
273
+ comment = nodes[i]
274
+ new_value = new_value.with_origin(new_value.origin.append_comments([comment.comment_text]))
275
+ break
276
+ elsif nodes[i].is_a?(ConfigNodeSingleToken)
277
+ curr = nodes[i]
278
+ if curr.token.equal?(Tokens::COMMA) || Tokens.ignored_whitespace?(curr.token)
279
+ # keep searching, as there could still be a comment
280
+ else
281
+ i -= 1
282
+ break
283
+ end
284
+ else
285
+ i -= 1
286
+ break
287
+ end
288
+ i += 1
289
+ end
290
+ end
291
+
292
+ @path_stack.shift
293
+
294
+ key = path.first
295
+ remaining = path.remainder
296
+
297
+ if remaining.nil?
298
+ existing = values[key]
299
+ unless existing.nil?
300
+ # In strict JSON, dups should be an error; while in
301
+ # our custom config language, they should be merged
302
+ # if the value is an object (or substitution that
303
+ # could become an object).
304
+
305
+ if @flavor.equal?(ConfigSyntax::JSON)
306
+ raise parse_error("JSON does not allow duplicate fields: '#{key}'" +
307
+ " was already seen at #{existing.origin().description()}")
308
+ else
309
+ new_value = new_value.with_fallback(existing)
310
+ end
311
+ end
312
+ values[key] = new_value
313
+ else
314
+ if @flavor == ConfigSyntax::JSON
315
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "somehow got multi-element path in JSON mode"
316
+ end
317
+
318
+ obj = create_value_under_path(remaining, new_value)
319
+ existing = values[key]
320
+ if !existing.nil?
321
+ obj = obj.with_fallback(existing)
322
+ end
323
+ values[key] = obj
324
+ end
325
+ end
326
+ i += 1
327
+ end
328
+
329
+ SimpleConfigObject.new(object_origin, values)
330
+ end
331
+
332
+ def parse_array(n)
333
+ @array_count += 1
334
+
335
+ array_origin = line_origin
336
+ values = []
337
+
338
+ last_was_new_line = false
339
+ comments = []
340
+
341
+ v = nil
342
+
343
+ n.children.each do |node|
344
+ if node.is_a?(ConfigNodeComment)
345
+ comments << node.comment_text
346
+ last_was_new_line = false
347
+ elsif node.is_a?(ConfigNodeSingleToken) && Tokens.newline?(node.token)
348
+ @line_number += 1
349
+ if last_was_new_line && v.nil?
350
+ comments.clear
351
+ elsif !v.nil?
352
+ values << v.with_origin(v.origin.append_comments(comments.clone))
353
+ comments.clear
354
+ v = nil
355
+ end
356
+ last_was_new_line = true
357
+ elsif node.is_a?(Hocon::Impl::AbstractConfigNodeValue)
358
+ last_was_new_line = false
359
+ unless v.nil?
360
+ values << v.with_origin(v.origin.append_comments(comments.clone))
361
+ comments.clear
362
+ end
363
+ v = parse_value(node, comments)
364
+ end
365
+ end
366
+ # There shouldn't be any comments at this point, but add them just in case
367
+ unless v.nil?
368
+ values << v.with_origin(v.origin.append_comments(comments.clone))
369
+ end
370
+ @array_count -= 1
371
+ SimpleConfigList.new(array_origin, values)
372
+ end
373
+
374
+ def parse
375
+ result = nil
376
+ comments = []
377
+ last_was_new_line = false
378
+ @document.children.each do |node|
379
+ if node.is_a?(ConfigNodeComment)
380
+ comments << node.comment_text
381
+ last_was_new_line = false
382
+ elsif node.is_a?(ConfigNodeSingleToken)
383
+ t = node.token
384
+ if Tokens.newline?(t)
385
+ @line_number += 1
386
+ if last_was_new_line && result.nil?
387
+ comments.clear
388
+ elsif !result.nil?
389
+ result = result.with_origin(result.origin.append_comments(comments.clone))
390
+ comments.clear
391
+ break
392
+ end
393
+ last_was_new_line = true
394
+ end
395
+ elsif node.is_a?(Hocon::Impl::ConfigNodeComplexValue)
396
+ result = parse_value(node, comments)
397
+ last_was_new_line = false
398
+ end
399
+ end
400
+ result
401
+ end
402
+ end
403
+ end