hocon 0.0.7 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|