diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hcl/json/parser.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/hcl/json/parser.go | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/hcl/json/parser.go b/vendor/github.com/hashicorp/hcl2/hcl/json/parser.go new file mode 100644 index 0000000..246fd1c --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hcl/json/parser.go | |||
@@ -0,0 +1,491 @@ | |||
1 | package json | ||
2 | |||
3 | import ( | ||
4 | "encoding/json" | ||
5 | "fmt" | ||
6 | "math/big" | ||
7 | |||
8 | "github.com/hashicorp/hcl2/hcl" | ||
9 | ) | ||
10 | |||
11 | func parseFileContent(buf []byte, filename string) (node, hcl.Diagnostics) { | ||
12 | tokens := scan(buf, pos{ | ||
13 | Filename: filename, | ||
14 | Pos: hcl.Pos{ | ||
15 | Byte: 0, | ||
16 | Line: 1, | ||
17 | Column: 1, | ||
18 | }, | ||
19 | }) | ||
20 | p := newPeeker(tokens) | ||
21 | node, diags := parseValue(p) | ||
22 | if len(diags) == 0 && p.Peek().Type != tokenEOF { | ||
23 | diags = diags.Append(&hcl.Diagnostic{ | ||
24 | Severity: hcl.DiagError, | ||
25 | Summary: "Extraneous data after value", | ||
26 | Detail: "Extra characters appear after the JSON value.", | ||
27 | Subject: p.Peek().Range.Ptr(), | ||
28 | }) | ||
29 | } | ||
30 | return node, diags | ||
31 | } | ||
32 | |||
33 | func parseValue(p *peeker) (node, hcl.Diagnostics) { | ||
34 | tok := p.Peek() | ||
35 | |||
36 | wrapInvalid := func(n node, diags hcl.Diagnostics) (node, hcl.Diagnostics) { | ||
37 | if n != nil { | ||
38 | return n, diags | ||
39 | } | ||
40 | return invalidVal{tok.Range}, diags | ||
41 | } | ||
42 | |||
43 | switch tok.Type { | ||
44 | case tokenBraceO: | ||
45 | return wrapInvalid(parseObject(p)) | ||
46 | case tokenBrackO: | ||
47 | return wrapInvalid(parseArray(p)) | ||
48 | case tokenNumber: | ||
49 | return wrapInvalid(parseNumber(p)) | ||
50 | case tokenString: | ||
51 | return wrapInvalid(parseString(p)) | ||
52 | case tokenKeyword: | ||
53 | return wrapInvalid(parseKeyword(p)) | ||
54 | case tokenBraceC: | ||
55 | return wrapInvalid(nil, hcl.Diagnostics{ | ||
56 | { | ||
57 | Severity: hcl.DiagError, | ||
58 | Summary: "Missing attribute value", | ||
59 | Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.", | ||
60 | Subject: &tok.Range, | ||
61 | }, | ||
62 | }) | ||
63 | case tokenBrackC: | ||
64 | return wrapInvalid(nil, hcl.Diagnostics{ | ||
65 | { | ||
66 | Severity: hcl.DiagError, | ||
67 | Summary: "Missing array element value", | ||
68 | Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.", | ||
69 | Subject: &tok.Range, | ||
70 | }, | ||
71 | }) | ||
72 | case tokenEOF: | ||
73 | return wrapInvalid(nil, hcl.Diagnostics{ | ||
74 | { | ||
75 | Severity: hcl.DiagError, | ||
76 | Summary: "Missing value", | ||
77 | Detail: "The JSON data ends prematurely.", | ||
78 | Subject: &tok.Range, | ||
79 | }, | ||
80 | }) | ||
81 | default: | ||
82 | return wrapInvalid(nil, hcl.Diagnostics{ | ||
83 | { | ||
84 | Severity: hcl.DiagError, | ||
85 | Summary: "Invalid start of value", | ||
86 | Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.", | ||
87 | Subject: &tok.Range, | ||
88 | }, | ||
89 | }) | ||
90 | } | ||
91 | } | ||
92 | |||
93 | func tokenCanStartValue(tok token) bool { | ||
94 | switch tok.Type { | ||
95 | case tokenBraceO, tokenBrackO, tokenNumber, tokenString, tokenKeyword: | ||
96 | return true | ||
97 | default: | ||
98 | return false | ||
99 | } | ||
100 | } | ||
101 | |||
102 | func parseObject(p *peeker) (node, hcl.Diagnostics) { | ||
103 | var diags hcl.Diagnostics | ||
104 | |||
105 | open := p.Read() | ||
106 | attrs := []*objectAttr{} | ||
107 | |||
108 | // recover is used to shift the peeker to what seems to be the end of | ||
109 | // our object, so that when we encounter an error we leave the peeker | ||
110 | // at a reasonable point in the token stream to continue parsing. | ||
111 | recover := func(tok token) { | ||
112 | open := 1 | ||
113 | for { | ||
114 | switch tok.Type { | ||
115 | case tokenBraceO: | ||
116 | open++ | ||
117 | case tokenBraceC: | ||
118 | open-- | ||
119 | if open <= 1 { | ||
120 | return | ||
121 | } | ||
122 | case tokenEOF: | ||
123 | // Ran out of source before we were able to recover, | ||
124 | // so we'll bail here and let the caller deal with it. | ||
125 | return | ||
126 | } | ||
127 | tok = p.Read() | ||
128 | } | ||
129 | } | ||
130 | |||
131 | Token: | ||
132 | for { | ||
133 | if p.Peek().Type == tokenBraceC { | ||
134 | break Token | ||
135 | } | ||
136 | |||
137 | keyNode, keyDiags := parseValue(p) | ||
138 | diags = diags.Extend(keyDiags) | ||
139 | if keyNode == nil { | ||
140 | return nil, diags | ||
141 | } | ||
142 | |||
143 | keyStrNode, ok := keyNode.(*stringVal) | ||
144 | if !ok { | ||
145 | return nil, diags.Append(&hcl.Diagnostic{ | ||
146 | Severity: hcl.DiagError, | ||
147 | Summary: "Invalid object attribute name", | ||
148 | Detail: "A JSON object attribute name must be a string", | ||
149 | Subject: keyNode.StartRange().Ptr(), | ||
150 | }) | ||
151 | } | ||
152 | |||
153 | key := keyStrNode.Value | ||
154 | |||
155 | colon := p.Read() | ||
156 | if colon.Type != tokenColon { | ||
157 | recover(colon) | ||
158 | |||
159 | if colon.Type == tokenBraceC || colon.Type == tokenComma { | ||
160 | // Catch common mistake of using braces instead of brackets | ||
161 | // for an object. | ||
162 | return nil, diags.Append(&hcl.Diagnostic{ | ||
163 | Severity: hcl.DiagError, | ||
164 | Summary: "Missing object value", | ||
165 | Detail: "A JSON object attribute must have a value, introduced by a colon.", | ||
166 | Subject: &colon.Range, | ||
167 | }) | ||
168 | } | ||
169 | |||
170 | if colon.Type == tokenEquals { | ||
171 | // Possible confusion with native HCL syntax. | ||
172 | return nil, diags.Append(&hcl.Diagnostic{ | ||
173 | Severity: hcl.DiagError, | ||
174 | Summary: "Missing attribute value colon", | ||
175 | Detail: "JSON uses a colon as its name/value delimiter, not an equals sign.", | ||
176 | Subject: &colon.Range, | ||
177 | }) | ||
178 | } | ||
179 | |||
180 | return nil, diags.Append(&hcl.Diagnostic{ | ||
181 | Severity: hcl.DiagError, | ||
182 | Summary: "Missing attribute value colon", | ||
183 | Detail: "A colon must appear between an object attribute's name and its value.", | ||
184 | Subject: &colon.Range, | ||
185 | }) | ||
186 | } | ||
187 | |||
188 | valNode, valDiags := parseValue(p) | ||
189 | diags = diags.Extend(valDiags) | ||
190 | if valNode == nil { | ||
191 | return nil, diags | ||
192 | } | ||
193 | |||
194 | attrs = append(attrs, &objectAttr{ | ||
195 | Name: key, | ||
196 | Value: valNode, | ||
197 | NameRange: keyStrNode.SrcRange, | ||
198 | }) | ||
199 | |||
200 | switch p.Peek().Type { | ||
201 | case tokenComma: | ||
202 | comma := p.Read() | ||
203 | if p.Peek().Type == tokenBraceC { | ||
204 | // Special error message for this common mistake | ||
205 | return nil, diags.Append(&hcl.Diagnostic{ | ||
206 | Severity: hcl.DiagError, | ||
207 | Summary: "Trailing comma in object", | ||
208 | Detail: "JSON does not permit a trailing comma after the final attribute in an object.", | ||
209 | Subject: &comma.Range, | ||
210 | }) | ||
211 | } | ||
212 | continue Token | ||
213 | case tokenEOF: | ||
214 | return nil, diags.Append(&hcl.Diagnostic{ | ||
215 | Severity: hcl.DiagError, | ||
216 | Summary: "Unclosed object", | ||
217 | Detail: "No closing brace was found for this JSON object.", | ||
218 | Subject: &open.Range, | ||
219 | }) | ||
220 | case tokenBrackC: | ||
221 | // Consume the bracket anyway, so that we don't return with the peeker | ||
222 | // at a strange place. | ||
223 | p.Read() | ||
224 | return nil, diags.Append(&hcl.Diagnostic{ | ||
225 | Severity: hcl.DiagError, | ||
226 | Summary: "Mismatched braces", | ||
227 | Detail: "A JSON object must be closed with a brace, not a bracket.", | ||
228 | Subject: p.Peek().Range.Ptr(), | ||
229 | }) | ||
230 | case tokenBraceC: | ||
231 | break Token | ||
232 | default: | ||
233 | recover(p.Read()) | ||
234 | return nil, diags.Append(&hcl.Diagnostic{ | ||
235 | Severity: hcl.DiagError, | ||
236 | Summary: "Missing attribute seperator comma", | ||
237 | Detail: "A comma must appear between each attribute declaration in an object.", | ||
238 | Subject: p.Peek().Range.Ptr(), | ||
239 | }) | ||
240 | } | ||
241 | |||
242 | } | ||
243 | |||
244 | close := p.Read() | ||
245 | return &objectVal{ | ||
246 | Attrs: attrs, | ||
247 | SrcRange: hcl.RangeBetween(open.Range, close.Range), | ||
248 | OpenRange: open.Range, | ||
249 | CloseRange: close.Range, | ||
250 | }, diags | ||
251 | } | ||
252 | |||
253 | func parseArray(p *peeker) (node, hcl.Diagnostics) { | ||
254 | var diags hcl.Diagnostics | ||
255 | |||
256 | open := p.Read() | ||
257 | vals := []node{} | ||
258 | |||
259 | // recover is used to shift the peeker to what seems to be the end of | ||
260 | // our array, so that when we encounter an error we leave the peeker | ||
261 | // at a reasonable point in the token stream to continue parsing. | ||
262 | recover := func(tok token) { | ||
263 | open := 1 | ||
264 | for { | ||
265 | switch tok.Type { | ||
266 | case tokenBrackO: | ||
267 | open++ | ||
268 | case tokenBrackC: | ||
269 | open-- | ||
270 | if open <= 1 { | ||
271 | return | ||
272 | } | ||
273 | case tokenEOF: | ||
274 | // Ran out of source before we were able to recover, | ||
275 | // so we'll bail here and let the caller deal with it. | ||
276 | return | ||
277 | } | ||
278 | tok = p.Read() | ||
279 | } | ||
280 | } | ||
281 | |||
282 | Token: | ||
283 | for { | ||
284 | if p.Peek().Type == tokenBrackC { | ||
285 | break Token | ||
286 | } | ||
287 | |||
288 | valNode, valDiags := parseValue(p) | ||
289 | diags = diags.Extend(valDiags) | ||
290 | if valNode == nil { | ||
291 | return nil, diags | ||
292 | } | ||
293 | |||
294 | vals = append(vals, valNode) | ||
295 | |||
296 | switch p.Peek().Type { | ||
297 | case tokenComma: | ||
298 | comma := p.Read() | ||
299 | if p.Peek().Type == tokenBrackC { | ||
300 | // Special error message for this common mistake | ||
301 | return nil, diags.Append(&hcl.Diagnostic{ | ||
302 | Severity: hcl.DiagError, | ||
303 | Summary: "Trailing comma in array", | ||
304 | Detail: "JSON does not permit a trailing comma after the final attribute in an array.", | ||
305 | Subject: &comma.Range, | ||
306 | }) | ||
307 | } | ||
308 | continue Token | ||
309 | case tokenColon: | ||
310 | recover(p.Read()) | ||
311 | return nil, diags.Append(&hcl.Diagnostic{ | ||
312 | Severity: hcl.DiagError, | ||
313 | Summary: "Invalid array value", | ||
314 | Detail: "A colon is not used to introduce values in a JSON array.", | ||
315 | Subject: p.Peek().Range.Ptr(), | ||
316 | }) | ||
317 | case tokenEOF: | ||
318 | recover(p.Read()) | ||
319 | return nil, diags.Append(&hcl.Diagnostic{ | ||
320 | Severity: hcl.DiagError, | ||
321 | Summary: "Unclosed object", | ||
322 | Detail: "No closing bracket was found for this JSON array.", | ||
323 | Subject: &open.Range, | ||
324 | }) | ||
325 | case tokenBraceC: | ||
326 | recover(p.Read()) | ||
327 | return nil, diags.Append(&hcl.Diagnostic{ | ||
328 | Severity: hcl.DiagError, | ||
329 | Summary: "Mismatched brackets", | ||
330 | Detail: "A JSON array must be closed with a bracket, not a brace.", | ||
331 | Subject: p.Peek().Range.Ptr(), | ||
332 | }) | ||
333 | case tokenBrackC: | ||
334 | break Token | ||
335 | default: | ||
336 | recover(p.Read()) | ||
337 | return nil, diags.Append(&hcl.Diagnostic{ | ||
338 | Severity: hcl.DiagError, | ||
339 | Summary: "Missing attribute seperator comma", | ||
340 | Detail: "A comma must appear between each value in an array.", | ||
341 | Subject: p.Peek().Range.Ptr(), | ||
342 | }) | ||
343 | } | ||
344 | |||
345 | } | ||
346 | |||
347 | close := p.Read() | ||
348 | return &arrayVal{ | ||
349 | Values: vals, | ||
350 | SrcRange: hcl.RangeBetween(open.Range, close.Range), | ||
351 | OpenRange: open.Range, | ||
352 | }, diags | ||
353 | } | ||
354 | |||
355 | func parseNumber(p *peeker) (node, hcl.Diagnostics) { | ||
356 | tok := p.Read() | ||
357 | |||
358 | // Use encoding/json to validate the number syntax. | ||
359 | // TODO: Do this more directly to produce better diagnostics. | ||
360 | var num json.Number | ||
361 | err := json.Unmarshal(tok.Bytes, &num) | ||
362 | if err != nil { | ||
363 | return nil, hcl.Diagnostics{ | ||
364 | { | ||
365 | Severity: hcl.DiagError, | ||
366 | Summary: "Invalid JSON number", | ||
367 | Detail: fmt.Sprintf("There is a syntax error in the given JSON number."), | ||
368 | Subject: &tok.Range, | ||
369 | }, | ||
370 | } | ||
371 | } | ||
372 | |||
373 | f, _, err := big.ParseFloat(string(num), 10, 512, big.ToNearestEven) | ||
374 | if err != nil { | ||
375 | // Should never happen if above passed, since JSON numbers are a subset | ||
376 | // of what big.Float can parse... | ||
377 | return nil, hcl.Diagnostics{ | ||
378 | { | ||
379 | Severity: hcl.DiagError, | ||
380 | Summary: "Invalid JSON number", | ||
381 | Detail: fmt.Sprintf("There is a syntax error in the given JSON number."), | ||
382 | Subject: &tok.Range, | ||
383 | }, | ||
384 | } | ||
385 | } | ||
386 | |||
387 | return &numberVal{ | ||
388 | Value: f, | ||
389 | SrcRange: tok.Range, | ||
390 | }, nil | ||
391 | } | ||
392 | |||
393 | func parseString(p *peeker) (node, hcl.Diagnostics) { | ||
394 | tok := p.Read() | ||
395 | var str string | ||
396 | err := json.Unmarshal(tok.Bytes, &str) | ||
397 | |||
398 | if err != nil { | ||
399 | var errRange hcl.Range | ||
400 | if serr, ok := err.(*json.SyntaxError); ok { | ||
401 | errOfs := serr.Offset | ||
402 | errPos := tok.Range.Start | ||
403 | errPos.Byte += int(errOfs) | ||
404 | |||
405 | // TODO: Use the byte offset to properly count unicode | ||
406 | // characters for the column, and mark the whole of the | ||
407 | // character that was wrong as part of our range. | ||
408 | errPos.Column += int(errOfs) | ||
409 | |||
410 | errEndPos := errPos | ||
411 | errEndPos.Byte++ | ||
412 | errEndPos.Column++ | ||
413 | |||
414 | errRange = hcl.Range{ | ||
415 | Filename: tok.Range.Filename, | ||
416 | Start: errPos, | ||
417 | End: errEndPos, | ||
418 | } | ||
419 | } else { | ||
420 | errRange = tok.Range | ||
421 | } | ||
422 | |||
423 | var contextRange *hcl.Range | ||
424 | if errRange != tok.Range { | ||
425 | contextRange = &tok.Range | ||
426 | } | ||
427 | |||
428 | // FIXME: Eventually we should parse strings directly here so | ||
429 | // we can produce a more useful error message in the face fo things | ||
430 | // such as invalid escapes, etc. | ||
431 | return nil, hcl.Diagnostics{ | ||
432 | { | ||
433 | Severity: hcl.DiagError, | ||
434 | Summary: "Invalid JSON string", | ||
435 | Detail: fmt.Sprintf("There is a syntax error in the given JSON string."), | ||
436 | Subject: &errRange, | ||
437 | Context: contextRange, | ||
438 | }, | ||
439 | } | ||
440 | } | ||
441 | |||
442 | return &stringVal{ | ||
443 | Value: str, | ||
444 | SrcRange: tok.Range, | ||
445 | }, nil | ||
446 | } | ||
447 | |||
448 | func parseKeyword(p *peeker) (node, hcl.Diagnostics) { | ||
449 | tok := p.Read() | ||
450 | s := string(tok.Bytes) | ||
451 | |||
452 | switch s { | ||
453 | case "true": | ||
454 | return &booleanVal{ | ||
455 | Value: true, | ||
456 | SrcRange: tok.Range, | ||
457 | }, nil | ||
458 | case "false": | ||
459 | return &booleanVal{ | ||
460 | Value: false, | ||
461 | SrcRange: tok.Range, | ||
462 | }, nil | ||
463 | case "null": | ||
464 | return &nullVal{ | ||
465 | SrcRange: tok.Range, | ||
466 | }, nil | ||
467 | case "undefined", "NaN", "Infinity": | ||
468 | return nil, hcl.Diagnostics{ | ||
469 | { | ||
470 | Severity: hcl.DiagError, | ||
471 | Summary: "Invalid JSON keyword", | ||
472 | Detail: fmt.Sprintf("The JavaScript identifier %q cannot be used in JSON.", s), | ||
473 | Subject: &tok.Range, | ||
474 | }, | ||
475 | } | ||
476 | default: | ||
477 | var dym string | ||
478 | if suggest := keywordSuggestion(s); suggest != "" { | ||
479 | dym = fmt.Sprintf(" Did you mean %q?", suggest) | ||
480 | } | ||
481 | |||
482 | return nil, hcl.Diagnostics{ | ||
483 | { | ||
484 | Severity: hcl.DiagError, | ||
485 | Summary: "Invalid JSON keyword", | ||
486 | Detail: fmt.Sprintf("%q is not a valid JSON keyword.%s", s, dym), | ||
487 | Subject: &tok.Range, | ||
488 | }, | ||
489 | } | ||
490 | } | ||
491 | } | ||