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.
- checksums.yaml +7 -0
- data/README.md +4 -2
- data/lib/hocon.rb +2 -0
- data/lib/hocon/config.rb +1010 -0
- data/lib/hocon/config_error.rb +32 -2
- data/lib/hocon/config_factory.rb +46 -0
- data/lib/hocon/config_include_context.rb +49 -0
- data/lib/hocon/config_includer_file.rb +27 -0
- data/lib/hocon/config_list.rb +49 -0
- data/lib/hocon/config_mergeable.rb +74 -0
- data/lib/hocon/config_object.rb +144 -1
- data/lib/hocon/config_parse_options.rb +33 -9
- data/lib/hocon/config_parseable.rb +51 -0
- data/lib/hocon/config_render_options.rb +4 -2
- data/lib/hocon/config_resolve_options.rb +31 -0
- data/lib/hocon/config_syntax.rb +5 -2
- data/lib/hocon/config_util.rb +73 -0
- data/lib/hocon/config_value.rb +122 -0
- data/lib/hocon/config_value_factory.rb +66 -2
- data/lib/hocon/config_value_type.rb +5 -2
- data/lib/hocon/impl.rb +2 -0
- data/lib/hocon/impl/abstract_config_node.rb +29 -0
- data/lib/hocon/impl/abstract_config_node_value.rb +11 -0
- data/lib/hocon/impl/abstract_config_object.rb +148 -42
- data/lib/hocon/impl/abstract_config_value.rb +251 -11
- data/lib/hocon/impl/array_iterator.rb +19 -0
- data/lib/hocon/impl/config_boolean.rb +7 -1
- data/lib/hocon/impl/config_concatenation.rb +177 -28
- data/lib/hocon/impl/config_delayed_merge.rb +329 -0
- data/lib/hocon/impl/config_delayed_merge_object.rb +274 -0
- data/lib/hocon/impl/config_document_parser.rb +647 -0
- data/lib/hocon/impl/config_double.rb +44 -0
- data/lib/hocon/impl/config_impl.rb +143 -19
- data/lib/hocon/impl/config_impl_util.rb +18 -0
- data/lib/hocon/impl/config_include_kind.rb +10 -0
- data/lib/hocon/impl/config_int.rb +13 -1
- data/lib/hocon/impl/config_node_array.rb +11 -0
- data/lib/hocon/impl/config_node_comment.rb +19 -0
- data/lib/hocon/impl/config_node_complex_value.rb +54 -0
- data/lib/hocon/impl/config_node_concatenation.rb +11 -0
- data/lib/hocon/impl/config_node_field.rb +81 -0
- data/lib/hocon/impl/config_node_include.rb +33 -0
- data/lib/hocon/impl/config_node_object.rb +276 -0
- data/lib/hocon/impl/config_node_path.rb +48 -0
- data/lib/hocon/impl/config_node_root.rb +60 -0
- data/lib/hocon/impl/config_node_simple_value.rb +42 -0
- data/lib/hocon/impl/config_node_single_token.rb +17 -0
- data/lib/hocon/impl/config_null.rb +15 -7
- data/lib/hocon/impl/config_number.rb +43 -4
- data/lib/hocon/impl/config_parser.rb +403 -0
- data/lib/hocon/impl/config_reference.rb +142 -0
- data/lib/hocon/impl/config_string.rb +55 -7
- data/lib/hocon/impl/container.rb +29 -0
- data/lib/hocon/impl/default_transformer.rb +24 -15
- data/lib/hocon/impl/from_map_mode.rb +3 -1
- data/lib/hocon/impl/full_includer.rb +2 -0
- data/lib/hocon/impl/memo_key.rb +42 -0
- data/lib/hocon/impl/mergeable_value.rb +8 -0
- data/lib/hocon/impl/origin_type.rb +8 -2
- data/lib/hocon/impl/parseable.rb +455 -91
- data/lib/hocon/impl/path.rb +181 -59
- data/lib/hocon/impl/path_builder.rb +24 -3
- data/lib/hocon/impl/path_parser.rb +280 -0
- data/lib/hocon/impl/replaceable_merge_stack.rb +22 -0
- data/lib/hocon/impl/resolve_context.rb +254 -0
- data/lib/hocon/impl/resolve_memos.rb +21 -0
- data/lib/hocon/impl/resolve_result.rb +39 -0
- data/lib/hocon/impl/resolve_source.rb +354 -0
- data/lib/hocon/impl/resolve_status.rb +3 -1
- data/lib/hocon/impl/simple_config.rb +264 -10
- data/lib/hocon/impl/simple_config_document.rb +48 -0
- data/lib/hocon/impl/simple_config_list.rb +282 -8
- data/lib/hocon/impl/simple_config_object.rb +424 -88
- data/lib/hocon/impl/simple_config_origin.rb +263 -71
- data/lib/hocon/impl/simple_include_context.rb +31 -1
- data/lib/hocon/impl/simple_includer.rb +196 -1
- data/lib/hocon/impl/substitution_expression.rb +38 -0
- data/lib/hocon/impl/token.rb +17 -4
- data/lib/hocon/impl/token_type.rb +6 -2
- data/lib/hocon/impl/tokenizer.rb +339 -109
- data/lib/hocon/impl/tokens.rb +330 -79
- data/lib/hocon/impl/unmergeable.rb +14 -1
- data/lib/hocon/impl/unsupported_operation_error.rb +6 -0
- data/lib/hocon/impl/url.rb +37 -0
- data/lib/hocon/parser.rb +7 -0
- data/lib/hocon/parser/config_document.rb +92 -0
- data/lib/hocon/parser/config_document_factory.rb +36 -0
- data/lib/hocon/parser/config_node.rb +30 -0
- metadata +67 -43
- data/lib/hocon/impl/config_float.rb +0 -13
- data/lib/hocon/impl/parser.rb +0 -977
- data/lib/hocon/impl/properties_parser.rb +0 -83
@@ -0,0 +1,274 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
require 'hocon/impl/unmergeable'
|
3
|
+
require 'hocon/impl/replaceable_merge_stack'
|
4
|
+
|
5
|
+
# This is just like ConfigDelayedMerge except we know statically
|
6
|
+
# that it will turn out to be an object.
|
7
|
+
class Hocon::Impl::ConfigDelayedMergeObject
|
8
|
+
include Hocon::Impl::Unmergeable
|
9
|
+
include Hocon::Impl::ReplaceableMergeStack
|
10
|
+
include Hocon::Impl::AbstractConfigObject
|
11
|
+
|
12
|
+
def initialize(origin, stack)
|
13
|
+
super(origin)
|
14
|
+
|
15
|
+
@stack = stack
|
16
|
+
|
17
|
+
if stack.empty?
|
18
|
+
raise Hocon::ConfigError::ConfigBugOrBrokenError.new("creating empty delayed merge value", nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
if !@stack[0].is_a? Hocon::Impl::AbstractConfigObject
|
22
|
+
error_message = "created a delayed merge object not guaranteed to be an object"
|
23
|
+
raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil)
|
24
|
+
end
|
25
|
+
|
26
|
+
stack.each do |v|
|
27
|
+
if v.is_a?(Hocon::Impl::ConfigDelayedMergeObject) || v.is_a?(Hocon::Impl::ConfigDelayedMergeObject)
|
28
|
+
error_message = "placed nested DelayedMerge in a ConfigDelayedMerge, should have consolidated stack"
|
29
|
+
raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :stack
|
35
|
+
|
36
|
+
def new_copy(status, origin)
|
37
|
+
if status != resolve_status
|
38
|
+
raise Hocon::ConfigError::ConfigBugOrBrokenError.new(
|
39
|
+
"attempt to create resolved ConfigDelayedMergeObject")
|
40
|
+
end
|
41
|
+
Hocon::Impl::ConfigDelayedMergeObject.new(origin, @stack)
|
42
|
+
end
|
43
|
+
|
44
|
+
def resolve_substitutions(context, source)
|
45
|
+
merged = Hocon::Impl::ConfigDelayedMerge.resolve_substitutions(self, @stack, context, source)
|
46
|
+
merged.as_object_result
|
47
|
+
end
|
48
|
+
|
49
|
+
def make_replacement(context, skipping)
|
50
|
+
Hocon::Impl::ConfigDelayedMerge.make_replacement(context, @stack, skipping)
|
51
|
+
end
|
52
|
+
|
53
|
+
def resolve_status
|
54
|
+
Hocon::Impl::ResolveStatus::UNRESOLVED
|
55
|
+
end
|
56
|
+
|
57
|
+
def replace_child(child, replacement)
|
58
|
+
new_stack = Hocon::Impl::AbstractConfigValue.replace_child_in_list(@stack, child, replacement)
|
59
|
+
if new_stack == nil
|
60
|
+
nil
|
61
|
+
else
|
62
|
+
self.class.new(origin, new_stack)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def has_descendant?(descendant)
|
67
|
+
Hocon::Impl::AbstractConfigValue.has_descendant_in_list?(@stack, descendant)
|
68
|
+
end
|
69
|
+
|
70
|
+
def relativized(prefix)
|
71
|
+
new_stack = []
|
72
|
+
@stack.each { |o|
|
73
|
+
new_stack << o.relativized(prefix)
|
74
|
+
}
|
75
|
+
self.class.new(origin, new_stack)
|
76
|
+
end
|
77
|
+
|
78
|
+
def ignores_fallbacks?
|
79
|
+
Hocon::Impl::ConfigDelayedMerge.stack_ignores_fallbacks?(@stack)
|
80
|
+
end
|
81
|
+
|
82
|
+
def merged_with_the_unmergeable(fallback)
|
83
|
+
require_not_ignoring_fallbacks
|
84
|
+
|
85
|
+
merged_stack_with_the_unmergeable(@stack, fallback)
|
86
|
+
end
|
87
|
+
|
88
|
+
def merged_with_object(fallback)
|
89
|
+
merged_with_non_object(fallback)
|
90
|
+
end
|
91
|
+
|
92
|
+
def merged_with_non_object(fallback)
|
93
|
+
require_not_ignoring_fallbacks
|
94
|
+
|
95
|
+
merged_stack_with_non_object(@stack, fallback)
|
96
|
+
end
|
97
|
+
|
98
|
+
# No implementation of withFallback here,
|
99
|
+
# just use the implementation in the super-class
|
100
|
+
|
101
|
+
def with_only_key(key)
|
102
|
+
raise self.class.not_resolved
|
103
|
+
end
|
104
|
+
|
105
|
+
def without_key(key)
|
106
|
+
raise self.class.not_resolved
|
107
|
+
end
|
108
|
+
|
109
|
+
def with_only_path_or_nil(key)
|
110
|
+
raise self.class.not_resolved
|
111
|
+
end
|
112
|
+
|
113
|
+
def with_only_path(key)
|
114
|
+
raise self.class.not_resolved
|
115
|
+
end
|
116
|
+
|
117
|
+
def without_path(key)
|
118
|
+
raise self.class.not_resolved
|
119
|
+
end
|
120
|
+
|
121
|
+
def with_value(key_or_path, value = nil)
|
122
|
+
raise self.class.not_resolved
|
123
|
+
end
|
124
|
+
|
125
|
+
def unmerged_values
|
126
|
+
@stack
|
127
|
+
end
|
128
|
+
|
129
|
+
def can_equal(other)
|
130
|
+
other.is_a? Hocon::Impl::ConfigDelayedMergeObject
|
131
|
+
end
|
132
|
+
|
133
|
+
def ==(other)
|
134
|
+
# note that "origin" is deliberately NOT part of equality
|
135
|
+
if other.is_a? Hocon::Impl::ConfigDelayedMergeObject
|
136
|
+
can_equal(other) && (@stack == other.stack || @stack.equal?(other.stack))
|
137
|
+
else
|
138
|
+
false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def hash
|
143
|
+
# note that "origin" is deliberately NOT part of equality
|
144
|
+
@stack.hash
|
145
|
+
end
|
146
|
+
|
147
|
+
def render_to_sb(sb, indent, at_root, at_key, options)
|
148
|
+
Hocon::Impl::ConfigDelayedMerge.render_value_to_sb_from_stack(@stack, sb, indent, at_root, at_key, options)
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.not_resolved
|
152
|
+
error_message = "need to Config#resolve() before using this object, see the API docs for Config#resolve()"
|
153
|
+
Hocon::ConfigError::ConfigNotResolvedError.new(error_message, nil)
|
154
|
+
end
|
155
|
+
|
156
|
+
def unwrapped
|
157
|
+
raise self.class.not_resolved
|
158
|
+
end
|
159
|
+
|
160
|
+
def [](key)
|
161
|
+
raise self.class.not_resolved
|
162
|
+
end
|
163
|
+
|
164
|
+
def has_key?(key)
|
165
|
+
raise self.class.not_resolved
|
166
|
+
end
|
167
|
+
|
168
|
+
def has_value?(value)
|
169
|
+
raise self.class.not_resolved
|
170
|
+
end
|
171
|
+
|
172
|
+
def each
|
173
|
+
raise self.class.not_resolved
|
174
|
+
end
|
175
|
+
|
176
|
+
def empty?
|
177
|
+
raise self.class.not_resolved
|
178
|
+
end
|
179
|
+
|
180
|
+
def keys
|
181
|
+
raise self.class.not_resolved
|
182
|
+
end
|
183
|
+
|
184
|
+
def values
|
185
|
+
raise self.class.not_resolved
|
186
|
+
end
|
187
|
+
|
188
|
+
def size
|
189
|
+
raise self.class.not_resolved
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.unmergeable?(object)
|
193
|
+
# Ruby note: This is the best way I could find to simulate
|
194
|
+
# else if (layer instanceof Unmergeable) in java since we're including
|
195
|
+
# the Unmergeable module instead of extending an Unmergeable class
|
196
|
+
object.class.included_modules.include?(Hocon::Impl::Unmergeable)
|
197
|
+
end
|
198
|
+
|
199
|
+
def attempt_peek_with_partial_resolve(key)
|
200
|
+
# a partial resolve of a ConfigDelayedMergeObject always results in a
|
201
|
+
# SimpleConfigObject because all the substitutions in the stack get
|
202
|
+
# resolved in order to look up the partial.
|
203
|
+
# So we know here that we have not been resolved at all even
|
204
|
+
# partially.
|
205
|
+
# Given that, all this code is probably gratuitous, since the app code
|
206
|
+
# is likely broken. But in general we only throw NotResolved if you try
|
207
|
+
# to touch the exact key that isn't resolved, so this is in that
|
208
|
+
# spirit.
|
209
|
+
|
210
|
+
# we'll be able to return a key if we have a value that ignores
|
211
|
+
# fallbacks, prior to any unmergeable values.
|
212
|
+
@stack.each do |layer|
|
213
|
+
if layer.is_a?(Hocon::Impl::AbstractConfigObject)
|
214
|
+
v = layer.attempt_peek_with_partial_resolve(key)
|
215
|
+
|
216
|
+
if !v.nil?
|
217
|
+
if v.ignores_fallbacks?
|
218
|
+
# we know we won't need to merge anything in to this
|
219
|
+
# value
|
220
|
+
return v
|
221
|
+
else
|
222
|
+
# we can't return this value because we know there are
|
223
|
+
# unmergeable values later in the stack that may
|
224
|
+
# contain values that need to be merged with this
|
225
|
+
# value. we'll throw the exception when we get to those
|
226
|
+
# unmergeable values, so continue here.
|
227
|
+
next
|
228
|
+
end
|
229
|
+
elsif self.class.unmergeable?(layer)
|
230
|
+
error_message = "should not be reached: unmergeable object returned null value"
|
231
|
+
raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil)
|
232
|
+
else
|
233
|
+
# a non-unmergeable AbstractConfigObject that returned null
|
234
|
+
# for the key in question is not relevant, we can keep
|
235
|
+
# looking for a value.
|
236
|
+
next
|
237
|
+
end
|
238
|
+
elsif self.class.unmergeable?(layer)
|
239
|
+
error_message = "Key '#{key}' is not available at '#{origin.description}'" +
|
240
|
+
"because value at '#{layer.origin.description}' has not been resolved" +
|
241
|
+
" and may turn out to contain or hide '#{key}'. Be sure to Config#resolve()" +
|
242
|
+
" before using a config object"
|
243
|
+
raise Hocon::ConfigError::ConfigNotResolvedError.new(error_message, nil)
|
244
|
+
elsif layer.resolved_status == ResolveStatus::UNRESOLVED
|
245
|
+
# if the layer is not an object, and not a substitution or
|
246
|
+
# merge,
|
247
|
+
# then it's something that's unresolved because it _contains_
|
248
|
+
# an unresolved object... i.e. it's an array
|
249
|
+
if !layer.is_a?(Hocon::Impl::ConfigList)
|
250
|
+
error_message = "Expecting a list here, not #{layer}"
|
251
|
+
raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil)
|
252
|
+
end
|
253
|
+
return nil
|
254
|
+
else
|
255
|
+
# non-object, but resolved, like an integer or something.
|
256
|
+
# has no children so the one we're after won't be in it.
|
257
|
+
# we would only have this in the stack in case something
|
258
|
+
# else "looks back" to it due to a cycle.
|
259
|
+
# anyway at this point we know we can't find the key anymore.
|
260
|
+
if !layer.ignores_fallbacks?
|
261
|
+
error_message = "resolved non-object should ignore fallbacks"
|
262
|
+
raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil)
|
263
|
+
end
|
264
|
+
return nil
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# If we get here, then we never found anything unresolved which means
|
269
|
+
# the ConfigDelayedMergeObject should not have existed. some
|
270
|
+
# invariant was violated.
|
271
|
+
error_message = "Delayed merge stack does not contain any unmergeable values"
|
272
|
+
raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil)
|
273
|
+
end
|
274
|
+
end
|
@@ -0,0 +1,647 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'hocon/impl'
|
5
|
+
require 'hocon/config_error'
|
6
|
+
require 'hocon/impl/tokens'
|
7
|
+
require 'hocon/impl/config_node_single_token'
|
8
|
+
require 'hocon/impl/config_node_comment'
|
9
|
+
require 'hocon/impl/abstract_config_node_value'
|
10
|
+
require 'hocon/impl/config_node_concatenation'
|
11
|
+
require 'hocon/impl/config_include_kind'
|
12
|
+
require 'hocon/impl/config_node_object'
|
13
|
+
require 'hocon/impl/config_node_array'
|
14
|
+
require 'hocon/impl/config_node_root'
|
15
|
+
|
16
|
+
class Hocon::Impl::ConfigDocumentParser
|
17
|
+
|
18
|
+
ConfigSyntax = Hocon::ConfigSyntax
|
19
|
+
ConfigParseError = Hocon::ConfigError::ConfigParseError
|
20
|
+
ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
|
21
|
+
ConfigValueType = Hocon::ConfigValueType
|
22
|
+
Tokens = Hocon::Impl::Tokens
|
23
|
+
PathParser = Hocon::Impl::PathParser
|
24
|
+
ArrayIterator = Hocon::Impl::ArrayIterator
|
25
|
+
ConfigImplUtil = Hocon::Impl::ConfigImplUtil
|
26
|
+
ConfigIncludeKind = Hocon::Impl::ConfigIncludeKind
|
27
|
+
ConfigNodeSingleToken = Hocon::Impl::ConfigNodeSingleToken
|
28
|
+
ConfigNodeSimpleValue = Hocon::Impl::ConfigNodeSimpleValue
|
29
|
+
ConfigNodeInclude = Hocon::Impl::ConfigNodeInclude
|
30
|
+
ConfigNodeField = Hocon::Impl::ConfigNodeField
|
31
|
+
ConfigNodeObject = Hocon::Impl::ConfigNodeObject
|
32
|
+
ConfigNodeArray = Hocon::Impl::ConfigNodeArray
|
33
|
+
ConfigNodeRoot = Hocon::Impl::ConfigNodeRoot
|
34
|
+
|
35
|
+
def self.parse(tokens, origin, options)
|
36
|
+
syntax = options.syntax.nil? ? ConfigSyntax::CONF : options.syntax
|
37
|
+
context = Hocon::Impl::ConfigDocumentParser::ParseContext.new(syntax, origin, tokens)
|
38
|
+
context.parse
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.parse_value(tokens, origin, options)
|
42
|
+
syntax = options.syntax.nil? ? ConfigSyntax::CONF : options.syntax
|
43
|
+
context = Hocon::Impl::ConfigDocumentParser::ParseContext.new(syntax, origin, tokens)
|
44
|
+
context.parse_single_value
|
45
|
+
end
|
46
|
+
|
47
|
+
class ParseContext
|
48
|
+
def initialize(flavor, origin, tokens)
|
49
|
+
@line_number = 1
|
50
|
+
@buffer = []
|
51
|
+
@tokens = tokens
|
52
|
+
@flavor = flavor
|
53
|
+
@equals_count = 0
|
54
|
+
@base_origin = origin
|
55
|
+
end
|
56
|
+
|
57
|
+
def pop_token
|
58
|
+
if @buffer.empty?
|
59
|
+
return @tokens.next
|
60
|
+
end
|
61
|
+
@buffer.pop
|
62
|
+
end
|
63
|
+
|
64
|
+
def next_token
|
65
|
+
t = pop_token
|
66
|
+
if @flavor.equal?(ConfigSyntax::JSON)
|
67
|
+
if Tokens.unquoted_text?(t) && !unquoted_whitespace?(t)
|
68
|
+
raise parse_error("Token not allowed in valid JSON: '#{Tokens.unquoted_text(t)}'")
|
69
|
+
elsif Tokens.substitution?(t)
|
70
|
+
raise parse_error("Substitutions (${} syntax) not allowed in JSON")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
t
|
74
|
+
end
|
75
|
+
|
76
|
+
def next_token_collecting_whitespace(nodes)
|
77
|
+
while true
|
78
|
+
t = next_token
|
79
|
+
if Tokens.ignored_whitespace?(t) || Tokens.newline?(t) || unquoted_whitespace?(t)
|
80
|
+
nodes.push(ConfigNodeSingleToken.new(t))
|
81
|
+
if Tokens.newline?(t)
|
82
|
+
@line_number = t.line_number + 1
|
83
|
+
end
|
84
|
+
elsif Tokens.comment?(t)
|
85
|
+
nodes.push(Hocon::Impl::ConfigNodeComment.new(t))
|
86
|
+
else
|
87
|
+
new_number = t.line_number
|
88
|
+
if new_number >= 0
|
89
|
+
@line_number = new_number
|
90
|
+
end
|
91
|
+
return t
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def put_back(token)
|
97
|
+
@buffer.push(token)
|
98
|
+
end
|
99
|
+
|
100
|
+
# In arrays and objects, comma can be omitted
|
101
|
+
# as long as there's at least one newline instead.
|
102
|
+
# this skips any newlines in front of a comma,
|
103
|
+
# skips the comma, and returns true if it found
|
104
|
+
# either a newline or a comma. The iterator
|
105
|
+
# is left just after the comma or the newline.
|
106
|
+
def check_element_separator(nodes)
|
107
|
+
if @flavor.equal?(ConfigSyntax::JSON)
|
108
|
+
t = next_token_collecting_whitespace(nodes)
|
109
|
+
if t.equal?(Tokens::COMMA)
|
110
|
+
nodes.push(ConfigNodeSingleToken.new(t))
|
111
|
+
return true
|
112
|
+
else
|
113
|
+
put_back(t)
|
114
|
+
return false
|
115
|
+
end
|
116
|
+
else
|
117
|
+
saw_separator_or_new_line = false
|
118
|
+
t = next_token
|
119
|
+
while true
|
120
|
+
if Tokens.ignored_whitespace?(t) || unquoted_whitespace?(t)
|
121
|
+
nodes.push(ConfigNodeSingleToken.new(t))
|
122
|
+
elsif Tokens.comment?(t)
|
123
|
+
nodes.push(Hocon::Impl::ConfigNodeComment.new(t))
|
124
|
+
elsif Tokens.newline?(t)
|
125
|
+
saw_separator_or_new_line = true
|
126
|
+
@line_number += 1
|
127
|
+
nodes.push(ConfigNodeSingleToken.new(t))
|
128
|
+
# we want to continue to also eat
|
129
|
+
# a comma if there is one.
|
130
|
+
elsif t.equal?(Tokens::COMMA)
|
131
|
+
nodes.push(ConfigNodeSingleToken.new(t))
|
132
|
+
return true
|
133
|
+
else
|
134
|
+
# non-newline-or-comma
|
135
|
+
put_back(t)
|
136
|
+
return saw_separator_or_new_line
|
137
|
+
end
|
138
|
+
t = next_token
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# parse a concatenation. If there is no concatenation, return the next value
|
144
|
+
def consolidate_values(nodes)
|
145
|
+
# this trick is not done in JSON
|
146
|
+
if @flavor.equal?(ConfigSyntax::JSON)
|
147
|
+
return nil
|
148
|
+
end
|
149
|
+
|
150
|
+
# create only if we have value tokens
|
151
|
+
values = []
|
152
|
+
value_count = 0
|
153
|
+
|
154
|
+
# ignore a newline up front
|
155
|
+
t = next_token_collecting_whitespace(nodes)
|
156
|
+
while true
|
157
|
+
v = nil
|
158
|
+
if Tokens.ignored_whitespace?(t)
|
159
|
+
values.push(ConfigNodeSingleToken.new(t))
|
160
|
+
t = next_token
|
161
|
+
next
|
162
|
+
elsif Tokens.value?(t) || Tokens.unquoted_text?(t) || Tokens.substitution?(t) || t == Tokens::OPEN_CURLY || t == Tokens::OPEN_SQUARE
|
163
|
+
# there may be newlines _within_ the objects and arrays
|
164
|
+
v = parse_value(t)
|
165
|
+
value_count += 1
|
166
|
+
else
|
167
|
+
break
|
168
|
+
end
|
169
|
+
|
170
|
+
if v.nil?
|
171
|
+
raise ConfigBugOrBrokenError, "no value"
|
172
|
+
end
|
173
|
+
|
174
|
+
values.push(v)
|
175
|
+
t = next_token # but don't consolidate across a newline
|
176
|
+
end
|
177
|
+
|
178
|
+
put_back(t)
|
179
|
+
|
180
|
+
# No concatenation was seen, but a single value may have been parsed, so return it, and put back
|
181
|
+
# all succeeding tokens
|
182
|
+
if value_count < 2
|
183
|
+
value = nil
|
184
|
+
values.each do |node|
|
185
|
+
if node.is_a?(Hocon::Impl::AbstractConfigNodeValue)
|
186
|
+
value = node
|
187
|
+
elsif value.nil?
|
188
|
+
nodes.add(node)
|
189
|
+
else
|
190
|
+
put_back(node.tokens[0])
|
191
|
+
end
|
192
|
+
end
|
193
|
+
return value
|
194
|
+
end
|
195
|
+
|
196
|
+
# Put back any trailing whitespace, as the parent object is responsible for tracking
|
197
|
+
# any leading/trailing whitespace
|
198
|
+
for i in (0..values.size - 1).reverse_each
|
199
|
+
if values[i].is_a?(ConfigNodeSingleToken)
|
200
|
+
put_back(values[i].token)
|
201
|
+
values.delete_at(i)
|
202
|
+
else
|
203
|
+
break
|
204
|
+
end
|
205
|
+
end
|
206
|
+
Hocon::Impl::ConfigNodeConcatenation.new(values)
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_error(message, cause = nil)
|
210
|
+
ConfigParseError.new(@base_origin.with_line_number(@line_number), message, cause)
|
211
|
+
end
|
212
|
+
|
213
|
+
def add_quote_suggestion(bad_token, message, last_path = nil, inside_equals = nil)
|
214
|
+
if inside_equals.nil?
|
215
|
+
inside_equals = @equals_count > 0
|
216
|
+
end
|
217
|
+
|
218
|
+
previous_field_name = last_path != nil ? last_path.render : nil
|
219
|
+
|
220
|
+
if bad_token == Tokens::EOF.to_s
|
221
|
+
# EOF requires special handling for the error to make sense.
|
222
|
+
if previous_field_name != nil
|
223
|
+
part = "#{message} (if you intended '#{previous_field_name}'" +
|
224
|
+
"' to be part of a value, instead of a key, " +
|
225
|
+
"try adding double quotes around the whole value"
|
226
|
+
else
|
227
|
+
return message
|
228
|
+
end
|
229
|
+
else
|
230
|
+
if previous_field_name != nil
|
231
|
+
part = "#{message} (if you intended #{bad_token}" +
|
232
|
+
" to be part of the value for '#{previous_field_name}', " +
|
233
|
+
"try enclosing the value in double quotes"
|
234
|
+
else
|
235
|
+
part = "#{message} (if you intended #{bad_token}" +
|
236
|
+
" to be part of a key or string value, " +
|
237
|
+
"try enclosing the key or value in double quotes"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Don't have a special case to throw a message about changing the file to .properties, since
|
242
|
+
# we don't support that format
|
243
|
+
part
|
244
|
+
end
|
245
|
+
|
246
|
+
def parse_value(t)
|
247
|
+
v = nil
|
248
|
+
starting_equals_count = @equals_count
|
249
|
+
|
250
|
+
if Tokens.value?(t) || Tokens.unquoted_text?(t) || Tokens.substitution?(t)
|
251
|
+
v = Hocon::Impl::ConfigNodeSimpleValue.new(t)
|
252
|
+
elsif t.equal?(Tokens::OPEN_CURLY)
|
253
|
+
v = parse_object(true)
|
254
|
+
elsif t.equal?(Tokens::OPEN_SQUARE)
|
255
|
+
v = parse_array
|
256
|
+
else
|
257
|
+
raise parse_error(add_quote_suggestion(t.to_s, "Expecting a value but got wrong token: #{t}"))
|
258
|
+
end
|
259
|
+
|
260
|
+
if @equals_count != starting_equals_count
|
261
|
+
raise ConfigBugOrBrokenError, "Bug in config parser: unbalanced equals count"
|
262
|
+
end
|
263
|
+
|
264
|
+
v
|
265
|
+
end
|
266
|
+
|
267
|
+
def parse_key(token)
|
268
|
+
if @flavor.equal?(ConfigSyntax::JSON)
|
269
|
+
if Tokens.value_with_type?(token, ConfigValueType::STRING)
|
270
|
+
return PathParser.parse_path_node_expression(Hocon::Impl::ArrayIterator.new([token]), nil)
|
271
|
+
else
|
272
|
+
raise ConfigParseError, "Expecting close brace } or a field name here, got #{token}"
|
273
|
+
end
|
274
|
+
else
|
275
|
+
expression = []
|
276
|
+
t = token
|
277
|
+
while Tokens.value?(t) || Tokens.unquoted_text?(t)
|
278
|
+
expression.push(t)
|
279
|
+
t = next_token # note: don't cross a newline
|
280
|
+
end
|
281
|
+
|
282
|
+
if expression.empty?
|
283
|
+
raise parse_error("expecting a close brace or a field name here, got #{t}")
|
284
|
+
end
|
285
|
+
|
286
|
+
put_back(t) # put back the token we ended with
|
287
|
+
PathParser.parse_path_node_expression(ArrayIterator.new(expression), nil)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def include_keyword?(t)
|
292
|
+
Tokens.unquoted_text?(t) && Tokens.unquoted_text(t) == "include"
|
293
|
+
end
|
294
|
+
|
295
|
+
def unquoted_whitespace?(t)
|
296
|
+
unless Tokens.unquoted_text?(t)
|
297
|
+
return false
|
298
|
+
end
|
299
|
+
|
300
|
+
s = Tokens.unquoted_text(t)
|
301
|
+
|
302
|
+
s.each_char do |c|
|
303
|
+
unless ConfigImplUtil.whitespace?(c)
|
304
|
+
return false
|
305
|
+
end
|
306
|
+
end
|
307
|
+
true
|
308
|
+
end
|
309
|
+
|
310
|
+
def key_value_separator?(t)
|
311
|
+
if @flavor.equal?(ConfigSyntax::JSON)
|
312
|
+
t.equal?(Tokens::COLON)
|
313
|
+
else
|
314
|
+
t.equal?(Tokens::COLON) || t.equal?(Tokens::EQUALS) || t.equal?(Tokens::PLUS_EQUALS)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def parse_include(children)
|
319
|
+
t = next_token_collecting_whitespace(children)
|
320
|
+
|
321
|
+
# we either have a quoted string or the "file()" syntax
|
322
|
+
if Tokens.unquoted_text?(t)
|
323
|
+
# get foo(
|
324
|
+
kind_text = Tokens.unquoted_text(t)
|
325
|
+
|
326
|
+
if kind_text == "url("
|
327
|
+
kind = ConfigIncludeKind::URL
|
328
|
+
elsif kind_text == "file("
|
329
|
+
kind = ConfigIncludeKind::FILE
|
330
|
+
elsif kind_text == "classpath("
|
331
|
+
kind = ConfigIncludeKind::CLASSPATH
|
332
|
+
else
|
333
|
+
raise parse_error("expecting include parameter to be quoted filename, file(), classpath(), or url(). No spaces are allowed before the open paren. Not expecting: #{t}")
|
334
|
+
end
|
335
|
+
|
336
|
+
children.push(ConfigNodeSingleToken.new(t))
|
337
|
+
|
338
|
+
# skip space inside parens
|
339
|
+
t = next_token_collecting_whitespace(children)
|
340
|
+
|
341
|
+
# quoted string
|
342
|
+
unless Tokens.value_with_type?(t, ConfigValueType::STRING)
|
343
|
+
raise parse_error("expecting a quoted string inside file(), classpath(), or url(), rather than: #{t}")
|
344
|
+
end
|
345
|
+
children.push(ConfigNodeSimpleValue.new(t))
|
346
|
+
# skip space after string, inside parens
|
347
|
+
t = next_token_collecting_whitespace(children)
|
348
|
+
|
349
|
+
if Tokens.unquoted_text?(t) && Tokens.unquoted_text(t) == ")"
|
350
|
+
# OK, close paren
|
351
|
+
else
|
352
|
+
raise parse_error("expecting a close parentheses ')' here, not: #{t}")
|
353
|
+
end
|
354
|
+
ConfigNodeInclude.new(children, kind)
|
355
|
+
elsif Tokens.value_with_type?(t, ConfigValueType::STRING)
|
356
|
+
children.push(ConfigNodeSimpleValue.new(t))
|
357
|
+
ConfigNodeInclude.new(children, ConfigIncludeKind::HEURISTIC)
|
358
|
+
else
|
359
|
+
raise parse_error("include keyword is not followed by a quoted string, but by: #{t}")
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def parse_object(had_open_curly)
|
364
|
+
# invoked just after the OPEN_CURLY (or START, if !hadOpenCurly)
|
365
|
+
after_comma = false
|
366
|
+
last_path = nil
|
367
|
+
last_inside_equals = false
|
368
|
+
object_nodes = []
|
369
|
+
keys = Hash.new
|
370
|
+
if had_open_curly
|
371
|
+
object_nodes.push(ConfigNodeSingleToken.new(Tokens::OPEN_CURLY))
|
372
|
+
end
|
373
|
+
|
374
|
+
while true
|
375
|
+
t = next_token_collecting_whitespace(object_nodes)
|
376
|
+
if t.equal?(Tokens::CLOSE_CURLY)
|
377
|
+
if @flavor.equal?(ConfigSyntax::JSON) && after_comma
|
378
|
+
raise parse_error(add_quote_suggestion(t.to_s,
|
379
|
+
"expecting a field name after a comma, got a close brace } instead"))
|
380
|
+
elsif !had_open_curly
|
381
|
+
raise parse_error(add_quote_suggestion(t.to_s,
|
382
|
+
"unbalanced close brace '}' with no open brace"))
|
383
|
+
end
|
384
|
+
object_nodes.push(ConfigNodeSingleToken.new(Tokens::CLOSE_CURLY))
|
385
|
+
break
|
386
|
+
elsif t.equal?(Tokens::EOF) && !had_open_curly
|
387
|
+
put_back(t)
|
388
|
+
break
|
389
|
+
elsif !@flavor.equal?(ConfigSyntax::JSON) && include_keyword?(t)
|
390
|
+
include_nodes = []
|
391
|
+
include_nodes.push(ConfigNodeSingleToken.new(t))
|
392
|
+
object_nodes.push(parse_include(include_nodes))
|
393
|
+
after_comma = false
|
394
|
+
else
|
395
|
+
key_value_nodes = []
|
396
|
+
key_token = t
|
397
|
+
path = parse_key(key_token)
|
398
|
+
key_value_nodes.push(path)
|
399
|
+
after_key = next_token_collecting_whitespace(key_value_nodes)
|
400
|
+
inside_equals = false
|
401
|
+
|
402
|
+
if @flavor.equal?(ConfigSyntax::CONF) && after_key.equal?(Tokens::OPEN_CURLY)
|
403
|
+
# can omit the ':' or '=' before an object value
|
404
|
+
next_value = parse_value(after_key)
|
405
|
+
else
|
406
|
+
unless key_value_separator?(after_key)
|
407
|
+
raise parse_error(add_quote_suggestion(after_key.to_s,
|
408
|
+
"Key '#{path.render()}' may not be followed by token: #{after_key}"))
|
409
|
+
end
|
410
|
+
|
411
|
+
key_value_nodes.push(ConfigNodeSingleToken.new(after_key))
|
412
|
+
|
413
|
+
if after_key.equal?(Tokens::EQUALS)
|
414
|
+
inside_equals = true
|
415
|
+
@equals_count += 1
|
416
|
+
end
|
417
|
+
|
418
|
+
next_value = consolidate_values(key_value_nodes)
|
419
|
+
if next_value.nil?
|
420
|
+
next_value = parse_value(next_token_collecting_whitespace(key_value_nodes))
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
key_value_nodes.push(next_value)
|
425
|
+
if inside_equals
|
426
|
+
@equals_count -= 1
|
427
|
+
end
|
428
|
+
last_inside_equals = inside_equals
|
429
|
+
|
430
|
+
key = path.value.first
|
431
|
+
remaining = path.value.remainder
|
432
|
+
|
433
|
+
if remaining.nil?
|
434
|
+
existing = keys[key]
|
435
|
+
unless existing.nil?
|
436
|
+
# In strict JSON, dups should be an error; while in
|
437
|
+
# our custom config language, they should be merged
|
438
|
+
# if the value is an object (or substitution that
|
439
|
+
# could become an object).
|
440
|
+
|
441
|
+
if @flavor.equal?(ConfigSyntax::JSON)
|
442
|
+
raise parse_error("JSON does not allow duplicate fields: '#{key}' was already seen")
|
443
|
+
end
|
444
|
+
end
|
445
|
+
keys[key] = true
|
446
|
+
else
|
447
|
+
if @flavor.equal?(ConfigSyntax::JSON)
|
448
|
+
raise ConfigBugOrBrokenError, "somehow got multi-element path in JSON mode"
|
449
|
+
end
|
450
|
+
keys[key] = true
|
451
|
+
end
|
452
|
+
|
453
|
+
after_comma = false
|
454
|
+
object_nodes.push(ConfigNodeField.new(key_value_nodes))
|
455
|
+
end
|
456
|
+
|
457
|
+
if check_element_separator(object_nodes)
|
458
|
+
# continue looping
|
459
|
+
after_comma = true
|
460
|
+
else
|
461
|
+
t = next_token_collecting_whitespace(object_nodes)
|
462
|
+
if t.equal?(Tokens::CLOSE_CURLY)
|
463
|
+
unless had_open_curly
|
464
|
+
raise parse_error(add_quote_suggestion(t.to_s,
|
465
|
+
"unbalanced close brace '}' with no open brace",
|
466
|
+
last_path,
|
467
|
+
last_inside_equals,))
|
468
|
+
end
|
469
|
+
object_nodes.push(ConfigNodeSingleToken.new(t))
|
470
|
+
break
|
471
|
+
elsif had_open_curly
|
472
|
+
raise parse_error(add_quote_suggestion(t.to_s,
|
473
|
+
"Expecting close brace } or a comma, got #{t}",
|
474
|
+
last_path,
|
475
|
+
last_inside_equals,))
|
476
|
+
else
|
477
|
+
if t.equal?(Tokens::EOF)
|
478
|
+
put_back(t)
|
479
|
+
break
|
480
|
+
else
|
481
|
+
raise parse_error(add_quote_suggestion(t.to_s,
|
482
|
+
"Expecting close brace } or a comma, got #{t}",
|
483
|
+
last_path,
|
484
|
+
last_inside_equals,))
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
ConfigNodeObject.new(object_nodes)
|
491
|
+
end
|
492
|
+
|
493
|
+
def parse_array
|
494
|
+
children = []
|
495
|
+
children.push(ConfigNodeSingleToken.new(Tokens::OPEN_SQUARE))
|
496
|
+
# invoked just after the OPEN_SQUARE
|
497
|
+
t = nil
|
498
|
+
|
499
|
+
next_value = consolidate_values(children)
|
500
|
+
unless next_value.nil?
|
501
|
+
children.push(next_value)
|
502
|
+
else
|
503
|
+
t = next_token_collecting_whitespace(children)
|
504
|
+
|
505
|
+
# special-case the first element
|
506
|
+
if t.equal?(Tokens::CLOSE_SQUARE)
|
507
|
+
children.push(ConfigNodeSingleToken.new(t))
|
508
|
+
return ConfigNodeArray.new(children)
|
509
|
+
elsif Tokens.value?(t) || t.equal?(Tokens::OPEN_CURLY) ||
|
510
|
+
t.equal?(Tokens::OPEN_SQUARE) || Tokens.unquoted_text?(t) ||
|
511
|
+
Tokens.substitution?(t)
|
512
|
+
next_value = parse_value(t)
|
513
|
+
children.push(next_value)
|
514
|
+
else
|
515
|
+
raise parse_error("List should have ] or a first element after the open [, instead had token: #{t}" +
|
516
|
+
" (if you want #{t} to be part of a string value, then double-quote it)")
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
# now remaining elements
|
521
|
+
while true
|
522
|
+
# just after a value
|
523
|
+
if check_element_separator(children)
|
524
|
+
# comma (or newline equivalent) consumed
|
525
|
+
else
|
526
|
+
t = next_token_collecting_whitespace(children)
|
527
|
+
if t.equal?(Tokens::CLOSE_SQUARE)
|
528
|
+
children.push(ConfigNodeSingleToken.new(t))
|
529
|
+
return ConfigNodeArray.new(children)
|
530
|
+
else
|
531
|
+
raise parse_error("List should have ended with ] or had a comma, instead had token: #{t}" +
|
532
|
+
" (if you want #{t} to be part of a string value, then double-quote it)")
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
# now just after a comma
|
537
|
+
next_value = consolidate_values(children)
|
538
|
+
unless next_value.nil?
|
539
|
+
children.push(next_value)
|
540
|
+
else
|
541
|
+
t = next_token_collecting_whitespace(children)
|
542
|
+
if Tokens.value?(t) || t.equal?(Tokens::OPEN_CURLY) ||
|
543
|
+
t.equal?(Tokens::OPEN_SQUARE) || Tokens.unquoted_text?(t) ||
|
544
|
+
Tokens.substitution?(t)
|
545
|
+
next_value = parse_value(t)
|
546
|
+
children.push(next_value)
|
547
|
+
elsif !@flavor.equal?(ConfigSyntax::JSON) && t.equal?(Tokens::CLOSE_SQUARE)
|
548
|
+
# we allow one trailing comma
|
549
|
+
put_back(t)
|
550
|
+
else
|
551
|
+
raise parse_error("List should have had new element after a comma, instead had token: #{t}" +
|
552
|
+
" (if you want the comma or #{t} to be part of a string value, then double-quote it)")
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
def parse
|
559
|
+
children = []
|
560
|
+
t = next_token
|
561
|
+
if t.equal?(Tokens::START)
|
562
|
+
# OK
|
563
|
+
else
|
564
|
+
raise ConfigBugOrBrokenException, "token stream did not begin with START, had #{t}"
|
565
|
+
end
|
566
|
+
|
567
|
+
t = next_token_collecting_whitespace(children)
|
568
|
+
result = nil
|
569
|
+
missing_curly = false
|
570
|
+
if t.equal?(Tokens::OPEN_CURLY) || t.equal?(Tokens::OPEN_SQUARE)
|
571
|
+
result = parse_value(t)
|
572
|
+
else
|
573
|
+
if @flavor.equal?(ConfigSyntax::JSON)
|
574
|
+
if t.equal?(Tokens::EOF)
|
575
|
+
raise parse_error("Empty document")
|
576
|
+
else
|
577
|
+
raise parse_error("Document must have an object or array at root, unexpected token: #{t}")
|
578
|
+
end
|
579
|
+
else
|
580
|
+
# the root object can omit the surrounding braces.
|
581
|
+
# this token should be the first field's key, or part
|
582
|
+
# of it, so put it back.
|
583
|
+
put_back(t)
|
584
|
+
missing_curly = true
|
585
|
+
result = parse_object(false)
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
# Need to pull the children out of the resulting node so we can keep leading
|
590
|
+
# and trailing whitespace if this was a no-brace object. Otherwise, we need to add
|
591
|
+
# the result into the list of children.
|
592
|
+
if result.is_a?(ConfigNodeObject) && missing_curly
|
593
|
+
children += result.children
|
594
|
+
else
|
595
|
+
children.push(result)
|
596
|
+
end
|
597
|
+
t = next_token_collecting_whitespace(children)
|
598
|
+
if t.equal?(Tokens::EOF)
|
599
|
+
if missing_curly
|
600
|
+
# If there were no braces, the entire document should be treated as a single object
|
601
|
+
ConfigNodeRoot.new([ConfigNodeObject.new(children)], @base_origin)
|
602
|
+
else
|
603
|
+
ConfigNodeRoot.new(children, @base_origin)
|
604
|
+
end
|
605
|
+
else
|
606
|
+
raise parse_error("Document has trailing tokens after first object or array: #{t}")
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
# Parse a given input stream into a single value node. Used when doing a replace inside a ConfigDocument.
|
611
|
+
def parse_single_value
|
612
|
+
t = next_token
|
613
|
+
if t.equal?(Tokens::START)
|
614
|
+
# OK
|
615
|
+
else
|
616
|
+
raise ConfigBugOrBrokenError, "token stream did not begin with START, had #{t}"
|
617
|
+
end
|
618
|
+
|
619
|
+
t = next_token
|
620
|
+
if Tokens.ignored_whitespace?(t) || Tokens.newline?(t) || unquoted_whitespace?(t) || Tokens.comment?(t)
|
621
|
+
raise parse_error("The value from setValue cannot have leading or trailing newlines, whitespace, or comments")
|
622
|
+
end
|
623
|
+
if t.equal?(Tokens::EOF)
|
624
|
+
raise parse_error("Empty value")
|
625
|
+
end
|
626
|
+
if @flavor.equal?(ConfigSyntax::JSON)
|
627
|
+
node = parse_value(t)
|
628
|
+
t = next_token
|
629
|
+
if t.equal?(Tokens::EOF)
|
630
|
+
return node
|
631
|
+
else
|
632
|
+
raise parse_error("Parsing JSON and the value set in setValue was either a concatenation or had trailing whitespace, newlines, or comments")
|
633
|
+
end
|
634
|
+
else
|
635
|
+
put_back(t)
|
636
|
+
nodes = []
|
637
|
+
node = consolidate_values(nodes)
|
638
|
+
t = next_token
|
639
|
+
if t.equal?(Tokens::EOF)
|
640
|
+
node
|
641
|
+
else
|
642
|
+
raise parse_error("The value from setValue cannot have leading or trailing newlines, whitespace, or comments")
|
643
|
+
end
|
644
|
+
end
|
645
|
+
end
|
646
|
+
end
|
647
|
+
end
|