hocon 0.0.7 → 0.1.0

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.
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