diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser_template.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser_template.go | 728 |
1 files changed, 728 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser_template.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser_template.go new file mode 100644 index 0000000..3711067 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser_template.go | |||
@@ -0,0 +1,728 @@ | |||
1 | package hclsyntax | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strings" | ||
6 | "unicode" | ||
7 | |||
8 | "github.com/hashicorp/hcl2/hcl" | ||
9 | "github.com/zclconf/go-cty/cty" | ||
10 | ) | ||
11 | |||
12 | func (p *parser) ParseTemplate() (Expression, hcl.Diagnostics) { | ||
13 | return p.parseTemplate(TokenEOF) | ||
14 | } | ||
15 | |||
16 | func (p *parser) parseTemplate(end TokenType) (Expression, hcl.Diagnostics) { | ||
17 | exprs, passthru, rng, diags := p.parseTemplateInner(end) | ||
18 | |||
19 | if passthru { | ||
20 | if len(exprs) != 1 { | ||
21 | panic("passthru set with len(exprs) != 1") | ||
22 | } | ||
23 | return &TemplateWrapExpr{ | ||
24 | Wrapped: exprs[0], | ||
25 | SrcRange: rng, | ||
26 | }, diags | ||
27 | } | ||
28 | |||
29 | return &TemplateExpr{ | ||
30 | Parts: exprs, | ||
31 | SrcRange: rng, | ||
32 | }, diags | ||
33 | } | ||
34 | |||
35 | func (p *parser) parseTemplateInner(end TokenType) ([]Expression, bool, hcl.Range, hcl.Diagnostics) { | ||
36 | parts, diags := p.parseTemplateParts(end) | ||
37 | tp := templateParser{ | ||
38 | Tokens: parts.Tokens, | ||
39 | SrcRange: parts.SrcRange, | ||
40 | } | ||
41 | exprs, exprsDiags := tp.parseRoot() | ||
42 | diags = append(diags, exprsDiags...) | ||
43 | |||
44 | passthru := false | ||
45 | if len(parts.Tokens) == 2 { // one real token and one synthetic "end" token | ||
46 | if _, isInterp := parts.Tokens[0].(*templateInterpToken); isInterp { | ||
47 | passthru = true | ||
48 | } | ||
49 | } | ||
50 | |||
51 | return exprs, passthru, parts.SrcRange, diags | ||
52 | } | ||
53 | |||
54 | type templateParser struct { | ||
55 | Tokens []templateToken | ||
56 | SrcRange hcl.Range | ||
57 | |||
58 | pos int | ||
59 | } | ||
60 | |||
61 | func (p *templateParser) parseRoot() ([]Expression, hcl.Diagnostics) { | ||
62 | var exprs []Expression | ||
63 | var diags hcl.Diagnostics | ||
64 | |||
65 | for { | ||
66 | next := p.Peek() | ||
67 | if _, isEnd := next.(*templateEndToken); isEnd { | ||
68 | break | ||
69 | } | ||
70 | |||
71 | expr, exprDiags := p.parseExpr() | ||
72 | diags = append(diags, exprDiags...) | ||
73 | exprs = append(exprs, expr) | ||
74 | } | ||
75 | |||
76 | return exprs, diags | ||
77 | } | ||
78 | |||
79 | func (p *templateParser) parseExpr() (Expression, hcl.Diagnostics) { | ||
80 | next := p.Peek() | ||
81 | switch tok := next.(type) { | ||
82 | |||
83 | case *templateLiteralToken: | ||
84 | p.Read() // eat literal | ||
85 | return &LiteralValueExpr{ | ||
86 | Val: cty.StringVal(tok.Val), | ||
87 | SrcRange: tok.SrcRange, | ||
88 | }, nil | ||
89 | |||
90 | case *templateInterpToken: | ||
91 | p.Read() // eat interp | ||
92 | return tok.Expr, nil | ||
93 | |||
94 | case *templateIfToken: | ||
95 | return p.parseIf() | ||
96 | |||
97 | case *templateForToken: | ||
98 | return p.parseFor() | ||
99 | |||
100 | case *templateEndToken: | ||
101 | p.Read() // eat erroneous token | ||
102 | return errPlaceholderExpr(tok.SrcRange), hcl.Diagnostics{ | ||
103 | { | ||
104 | // This is a particularly unhelpful diagnostic, so callers | ||
105 | // should attempt to pre-empt it and produce a more helpful | ||
106 | // diagnostic that is context-aware. | ||
107 | Severity: hcl.DiagError, | ||
108 | Summary: "Unexpected end of template", | ||
109 | Detail: "The control directives within this template are unbalanced.", | ||
110 | Subject: &tok.SrcRange, | ||
111 | }, | ||
112 | } | ||
113 | |||
114 | case *templateEndCtrlToken: | ||
115 | p.Read() // eat erroneous token | ||
116 | return errPlaceholderExpr(tok.SrcRange), hcl.Diagnostics{ | ||
117 | { | ||
118 | Severity: hcl.DiagError, | ||
119 | Summary: fmt.Sprintf("Unexpected %s directive", tok.Name()), | ||
120 | Detail: "The control directives within this template are unbalanced.", | ||
121 | Subject: &tok.SrcRange, | ||
122 | }, | ||
123 | } | ||
124 | |||
125 | default: | ||
126 | // should never happen, because above should be exhaustive | ||
127 | panic(fmt.Sprintf("unhandled template token type %T", next)) | ||
128 | } | ||
129 | } | ||
130 | |||
131 | func (p *templateParser) parseIf() (Expression, hcl.Diagnostics) { | ||
132 | open := p.Read() | ||
133 | openIf, isIf := open.(*templateIfToken) | ||
134 | if !isIf { | ||
135 | // should never happen if caller is behaving | ||
136 | panic("parseIf called with peeker not pointing at if token") | ||
137 | } | ||
138 | |||
139 | var ifExprs, elseExprs []Expression | ||
140 | var diags hcl.Diagnostics | ||
141 | var endifRange hcl.Range | ||
142 | |||
143 | currentExprs := &ifExprs | ||
144 | Token: | ||
145 | for { | ||
146 | next := p.Peek() | ||
147 | if end, isEnd := next.(*templateEndToken); isEnd { | ||
148 | diags = append(diags, &hcl.Diagnostic{ | ||
149 | Severity: hcl.DiagError, | ||
150 | Summary: "Unexpected end of template", | ||
151 | Detail: fmt.Sprintf( | ||
152 | "The if directive at %s is missing its corresponding endif directive.", | ||
153 | openIf.SrcRange, | ||
154 | ), | ||
155 | Subject: &end.SrcRange, | ||
156 | }) | ||
157 | return errPlaceholderExpr(end.SrcRange), diags | ||
158 | } | ||
159 | if end, isCtrlEnd := next.(*templateEndCtrlToken); isCtrlEnd { | ||
160 | p.Read() // eat end directive | ||
161 | |||
162 | switch end.Type { | ||
163 | |||
164 | case templateElse: | ||
165 | if currentExprs == &ifExprs { | ||
166 | currentExprs = &elseExprs | ||
167 | continue Token | ||
168 | } | ||
169 | |||
170 | diags = append(diags, &hcl.Diagnostic{ | ||
171 | Severity: hcl.DiagError, | ||
172 | Summary: "Unexpected else directive", | ||
173 | Detail: fmt.Sprintf( | ||
174 | "Already in the else clause for the if started at %s.", | ||
175 | openIf.SrcRange, | ||
176 | ), | ||
177 | Subject: &end.SrcRange, | ||
178 | }) | ||
179 | |||
180 | case templateEndIf: | ||
181 | endifRange = end.SrcRange | ||
182 | break Token | ||
183 | |||
184 | default: | ||
185 | diags = append(diags, &hcl.Diagnostic{ | ||
186 | Severity: hcl.DiagError, | ||
187 | Summary: fmt.Sprintf("Unexpected %s directive", end.Name()), | ||
188 | Detail: fmt.Sprintf( | ||
189 | "Expecting an endif directive for the if started at %s.", | ||
190 | openIf.SrcRange, | ||
191 | ), | ||
192 | Subject: &end.SrcRange, | ||
193 | }) | ||
194 | } | ||
195 | |||
196 | return errPlaceholderExpr(end.SrcRange), diags | ||
197 | } | ||
198 | |||
199 | expr, exprDiags := p.parseExpr() | ||
200 | diags = append(diags, exprDiags...) | ||
201 | *currentExprs = append(*currentExprs, expr) | ||
202 | } | ||
203 | |||
204 | if len(ifExprs) == 0 { | ||
205 | ifExprs = append(ifExprs, &LiteralValueExpr{ | ||
206 | Val: cty.StringVal(""), | ||
207 | SrcRange: hcl.Range{ | ||
208 | Filename: openIf.SrcRange.Filename, | ||
209 | Start: openIf.SrcRange.End, | ||
210 | End: openIf.SrcRange.End, | ||
211 | }, | ||
212 | }) | ||
213 | } | ||
214 | if len(elseExprs) == 0 { | ||
215 | elseExprs = append(elseExprs, &LiteralValueExpr{ | ||
216 | Val: cty.StringVal(""), | ||
217 | SrcRange: hcl.Range{ | ||
218 | Filename: endifRange.Filename, | ||
219 | Start: endifRange.Start, | ||
220 | End: endifRange.Start, | ||
221 | }, | ||
222 | }) | ||
223 | } | ||
224 | |||
225 | trueExpr := &TemplateExpr{ | ||
226 | Parts: ifExprs, | ||
227 | SrcRange: hcl.RangeBetween(ifExprs[0].Range(), ifExprs[len(ifExprs)-1].Range()), | ||
228 | } | ||
229 | falseExpr := &TemplateExpr{ | ||
230 | Parts: elseExprs, | ||
231 | SrcRange: hcl.RangeBetween(elseExprs[0].Range(), elseExprs[len(elseExprs)-1].Range()), | ||
232 | } | ||
233 | |||
234 | return &ConditionalExpr{ | ||
235 | Condition: openIf.CondExpr, | ||
236 | TrueResult: trueExpr, | ||
237 | FalseResult: falseExpr, | ||
238 | |||
239 | SrcRange: hcl.RangeBetween(openIf.SrcRange, endifRange), | ||
240 | }, diags | ||
241 | } | ||
242 | |||
243 | func (p *templateParser) parseFor() (Expression, hcl.Diagnostics) { | ||
244 | open := p.Read() | ||
245 | openFor, isFor := open.(*templateForToken) | ||
246 | if !isFor { | ||
247 | // should never happen if caller is behaving | ||
248 | panic("parseFor called with peeker not pointing at for token") | ||
249 | } | ||
250 | |||
251 | var contentExprs []Expression | ||
252 | var diags hcl.Diagnostics | ||
253 | var endforRange hcl.Range | ||
254 | |||
255 | Token: | ||
256 | for { | ||
257 | next := p.Peek() | ||
258 | if end, isEnd := next.(*templateEndToken); isEnd { | ||
259 | diags = append(diags, &hcl.Diagnostic{ | ||
260 | Severity: hcl.DiagError, | ||
261 | Summary: "Unexpected end of template", | ||
262 | Detail: fmt.Sprintf( | ||
263 | "The for directive at %s is missing its corresponding endfor directive.", | ||
264 | openFor.SrcRange, | ||
265 | ), | ||
266 | Subject: &end.SrcRange, | ||
267 | }) | ||
268 | return errPlaceholderExpr(end.SrcRange), diags | ||
269 | } | ||
270 | if end, isCtrlEnd := next.(*templateEndCtrlToken); isCtrlEnd { | ||
271 | p.Read() // eat end directive | ||
272 | |||
273 | switch end.Type { | ||
274 | |||
275 | case templateElse: | ||
276 | diags = append(diags, &hcl.Diagnostic{ | ||
277 | Severity: hcl.DiagError, | ||
278 | Summary: "Unexpected else directive", | ||
279 | Detail: "An else clause is not expected for a for directive.", | ||
280 | Subject: &end.SrcRange, | ||
281 | }) | ||
282 | |||
283 | case templateEndFor: | ||
284 | endforRange = end.SrcRange | ||
285 | break Token | ||
286 | |||
287 | default: | ||
288 | diags = append(diags, &hcl.Diagnostic{ | ||
289 | Severity: hcl.DiagError, | ||
290 | Summary: fmt.Sprintf("Unexpected %s directive", end.Name()), | ||
291 | Detail: fmt.Sprintf( | ||
292 | "Expecting an endfor directive corresponding to the for directive at %s.", | ||
293 | openFor.SrcRange, | ||
294 | ), | ||
295 | Subject: &end.SrcRange, | ||
296 | }) | ||
297 | } | ||
298 | |||
299 | return errPlaceholderExpr(end.SrcRange), diags | ||
300 | } | ||
301 | |||
302 | expr, exprDiags := p.parseExpr() | ||
303 | diags = append(diags, exprDiags...) | ||
304 | contentExprs = append(contentExprs, expr) | ||
305 | } | ||
306 | |||
307 | if len(contentExprs) == 0 { | ||
308 | contentExprs = append(contentExprs, &LiteralValueExpr{ | ||
309 | Val: cty.StringVal(""), | ||
310 | SrcRange: hcl.Range{ | ||
311 | Filename: openFor.SrcRange.Filename, | ||
312 | Start: openFor.SrcRange.End, | ||
313 | End: openFor.SrcRange.End, | ||
314 | }, | ||
315 | }) | ||
316 | } | ||
317 | |||
318 | contentExpr := &TemplateExpr{ | ||
319 | Parts: contentExprs, | ||
320 | SrcRange: hcl.RangeBetween(contentExprs[0].Range(), contentExprs[len(contentExprs)-1].Range()), | ||
321 | } | ||
322 | |||
323 | forExpr := &ForExpr{ | ||
324 | KeyVar: openFor.KeyVar, | ||
325 | ValVar: openFor.ValVar, | ||
326 | |||
327 | CollExpr: openFor.CollExpr, | ||
328 | ValExpr: contentExpr, | ||
329 | |||
330 | SrcRange: hcl.RangeBetween(openFor.SrcRange, endforRange), | ||
331 | OpenRange: openFor.SrcRange, | ||
332 | CloseRange: endforRange, | ||
333 | } | ||
334 | |||
335 | return &TemplateJoinExpr{ | ||
336 | Tuple: forExpr, | ||
337 | }, diags | ||
338 | } | ||
339 | |||
340 | func (p *templateParser) Peek() templateToken { | ||
341 | return p.Tokens[p.pos] | ||
342 | } | ||
343 | |||
344 | func (p *templateParser) Read() templateToken { | ||
345 | ret := p.Peek() | ||
346 | if _, end := ret.(*templateEndToken); !end { | ||
347 | p.pos++ | ||
348 | } | ||
349 | return ret | ||
350 | } | ||
351 | |||
352 | // parseTemplateParts produces a flat sequence of "template tokens", which are | ||
353 | // either literal values (with any "trimming" already applied), interpolation | ||
354 | // sequences, or control flow markers. | ||
355 | // | ||
356 | // A further pass is required on the result to turn it into an AST. | ||
357 | func (p *parser) parseTemplateParts(end TokenType) (*templateParts, hcl.Diagnostics) { | ||
358 | var parts []templateToken | ||
359 | var diags hcl.Diagnostics | ||
360 | |||
361 | startRange := p.NextRange() | ||
362 | ltrimNext := false | ||
363 | nextCanTrimPrev := false | ||
364 | var endRange hcl.Range | ||
365 | |||
366 | Token: | ||
367 | for { | ||
368 | next := p.Read() | ||
369 | if next.Type == end { | ||
370 | // all done! | ||
371 | endRange = next.Range | ||
372 | break | ||
373 | } | ||
374 | |||
375 | ltrim := ltrimNext | ||
376 | ltrimNext = false | ||
377 | canTrimPrev := nextCanTrimPrev | ||
378 | nextCanTrimPrev = false | ||
379 | |||
380 | switch next.Type { | ||
381 | case TokenStringLit, TokenQuotedLit: | ||
382 | str, strDiags := p.decodeStringLit(next) | ||
383 | diags = append(diags, strDiags...) | ||
384 | |||
385 | if ltrim { | ||
386 | str = strings.TrimLeftFunc(str, unicode.IsSpace) | ||
387 | } | ||
388 | |||
389 | parts = append(parts, &templateLiteralToken{ | ||
390 | Val: str, | ||
391 | SrcRange: next.Range, | ||
392 | }) | ||
393 | nextCanTrimPrev = true | ||
394 | |||
395 | case TokenTemplateInterp: | ||
396 | // if the opener is ${~ then we want to eat any trailing whitespace | ||
397 | // in the preceding literal token, assuming it is indeed a literal | ||
398 | // token. | ||
399 | if canTrimPrev && len(next.Bytes) == 3 && next.Bytes[2] == '~' && len(parts) > 0 { | ||
400 | prevExpr := parts[len(parts)-1] | ||
401 | if lexpr, ok := prevExpr.(*templateLiteralToken); ok { | ||
402 | lexpr.Val = strings.TrimRightFunc(lexpr.Val, unicode.IsSpace) | ||
403 | } | ||
404 | } | ||
405 | |||
406 | p.PushIncludeNewlines(false) | ||
407 | expr, exprDiags := p.ParseExpression() | ||
408 | diags = append(diags, exprDiags...) | ||
409 | close := p.Peek() | ||
410 | if close.Type != TokenTemplateSeqEnd { | ||
411 | if !p.recovery { | ||
412 | diags = append(diags, &hcl.Diagnostic{ | ||
413 | Severity: hcl.DiagError, | ||
414 | Summary: "Extra characters after interpolation expression", | ||
415 | Detail: "Expected a closing brace to end the interpolation expression, but found extra characters.", | ||
416 | Subject: &close.Range, | ||
417 | Context: hcl.RangeBetween(startRange, close.Range).Ptr(), | ||
418 | }) | ||
419 | } | ||
420 | p.recover(TokenTemplateSeqEnd) | ||
421 | } else { | ||
422 | p.Read() // eat closing brace | ||
423 | |||
424 | // If the closer is ~} then we want to eat any leading | ||
425 | // whitespace on the next token, if it turns out to be a | ||
426 | // literal token. | ||
427 | if len(close.Bytes) == 2 && close.Bytes[0] == '~' { | ||
428 | ltrimNext = true | ||
429 | } | ||
430 | } | ||
431 | p.PopIncludeNewlines() | ||
432 | parts = append(parts, &templateInterpToken{ | ||
433 | Expr: expr, | ||
434 | SrcRange: hcl.RangeBetween(next.Range, close.Range), | ||
435 | }) | ||
436 | |||
437 | case TokenTemplateControl: | ||
438 | // if the opener is %{~ then we want to eat any trailing whitespace | ||
439 | // in the preceding literal token, assuming it is indeed a literal | ||
440 | // token. | ||
441 | if canTrimPrev && len(next.Bytes) == 3 && next.Bytes[2] == '~' && len(parts) > 0 { | ||
442 | prevExpr := parts[len(parts)-1] | ||
443 | if lexpr, ok := prevExpr.(*templateLiteralToken); ok { | ||
444 | lexpr.Val = strings.TrimRightFunc(lexpr.Val, unicode.IsSpace) | ||
445 | } | ||
446 | } | ||
447 | p.PushIncludeNewlines(false) | ||
448 | |||
449 | kw := p.Peek() | ||
450 | if kw.Type != TokenIdent { | ||
451 | if !p.recovery { | ||
452 | diags = append(diags, &hcl.Diagnostic{ | ||
453 | Severity: hcl.DiagError, | ||
454 | Summary: "Invalid template directive", | ||
455 | Detail: "A template directive keyword (\"if\", \"for\", etc) is expected at the beginning of a %{ sequence.", | ||
456 | Subject: &kw.Range, | ||
457 | Context: hcl.RangeBetween(next.Range, kw.Range).Ptr(), | ||
458 | }) | ||
459 | } | ||
460 | p.recover(TokenTemplateSeqEnd) | ||
461 | p.PopIncludeNewlines() | ||
462 | continue Token | ||
463 | } | ||
464 | p.Read() // eat keyword token | ||
465 | |||
466 | switch { | ||
467 | |||
468 | case ifKeyword.TokenMatches(kw): | ||
469 | condExpr, exprDiags := p.ParseExpression() | ||
470 | diags = append(diags, exprDiags...) | ||
471 | parts = append(parts, &templateIfToken{ | ||
472 | CondExpr: condExpr, | ||
473 | SrcRange: hcl.RangeBetween(next.Range, p.NextRange()), | ||
474 | }) | ||
475 | |||
476 | case elseKeyword.TokenMatches(kw): | ||
477 | parts = append(parts, &templateEndCtrlToken{ | ||
478 | Type: templateElse, | ||
479 | SrcRange: hcl.RangeBetween(next.Range, p.NextRange()), | ||
480 | }) | ||
481 | |||
482 | case endifKeyword.TokenMatches(kw): | ||
483 | parts = append(parts, &templateEndCtrlToken{ | ||
484 | Type: templateEndIf, | ||
485 | SrcRange: hcl.RangeBetween(next.Range, p.NextRange()), | ||
486 | }) | ||
487 | |||
488 | case forKeyword.TokenMatches(kw): | ||
489 | var keyName, valName string | ||
490 | if p.Peek().Type != TokenIdent { | ||
491 | if !p.recovery { | ||
492 | diags = append(diags, &hcl.Diagnostic{ | ||
493 | Severity: hcl.DiagError, | ||
494 | Summary: "Invalid 'for' directive", | ||
495 | Detail: "For directive requires variable name after 'for'.", | ||
496 | Subject: p.Peek().Range.Ptr(), | ||
497 | }) | ||
498 | } | ||
499 | p.recover(TokenTemplateSeqEnd) | ||
500 | p.PopIncludeNewlines() | ||
501 | continue Token | ||
502 | } | ||
503 | |||
504 | valName = string(p.Read().Bytes) | ||
505 | |||
506 | if p.Peek().Type == TokenComma { | ||
507 | // What we just read was actually the key, then. | ||
508 | keyName = valName | ||
509 | p.Read() // eat comma | ||
510 | |||
511 | if p.Peek().Type != TokenIdent { | ||
512 | if !p.recovery { | ||
513 | diags = append(diags, &hcl.Diagnostic{ | ||
514 | Severity: hcl.DiagError, | ||
515 | Summary: "Invalid 'for' directive", | ||
516 | Detail: "For directive requires value variable name after comma.", | ||
517 | Subject: p.Peek().Range.Ptr(), | ||
518 | }) | ||
519 | } | ||
520 | p.recover(TokenTemplateSeqEnd) | ||
521 | p.PopIncludeNewlines() | ||
522 | continue Token | ||
523 | } | ||
524 | |||
525 | valName = string(p.Read().Bytes) | ||
526 | } | ||
527 | |||
528 | if !inKeyword.TokenMatches(p.Peek()) { | ||
529 | if !p.recovery { | ||
530 | diags = append(diags, &hcl.Diagnostic{ | ||
531 | Severity: hcl.DiagError, | ||
532 | Summary: "Invalid 'for' directive", | ||
533 | Detail: "For directive requires 'in' keyword after names.", | ||
534 | Subject: p.Peek().Range.Ptr(), | ||
535 | }) | ||
536 | } | ||
537 | p.recover(TokenTemplateSeqEnd) | ||
538 | p.PopIncludeNewlines() | ||
539 | continue Token | ||
540 | } | ||
541 | p.Read() // eat 'in' keyword | ||
542 | |||
543 | collExpr, collDiags := p.ParseExpression() | ||
544 | diags = append(diags, collDiags...) | ||
545 | parts = append(parts, &templateForToken{ | ||
546 | KeyVar: keyName, | ||
547 | ValVar: valName, | ||
548 | CollExpr: collExpr, | ||
549 | |||
550 | SrcRange: hcl.RangeBetween(next.Range, p.NextRange()), | ||
551 | }) | ||
552 | |||
553 | case endforKeyword.TokenMatches(kw): | ||
554 | parts = append(parts, &templateEndCtrlToken{ | ||
555 | Type: templateEndFor, | ||
556 | SrcRange: hcl.RangeBetween(next.Range, p.NextRange()), | ||
557 | }) | ||
558 | |||
559 | default: | ||
560 | if !p.recovery { | ||
561 | suggestions := []string{"if", "for", "else", "endif", "endfor"} | ||
562 | given := string(kw.Bytes) | ||
563 | suggestion := nameSuggestion(given, suggestions) | ||
564 | if suggestion != "" { | ||
565 | suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) | ||
566 | } | ||
567 | |||
568 | diags = append(diags, &hcl.Diagnostic{ | ||
569 | Severity: hcl.DiagError, | ||
570 | Summary: "Invalid template control keyword", | ||
571 | Detail: fmt.Sprintf("%q is not a valid template control keyword.%s", given, suggestion), | ||
572 | Subject: &kw.Range, | ||
573 | Context: hcl.RangeBetween(next.Range, kw.Range).Ptr(), | ||
574 | }) | ||
575 | } | ||
576 | p.recover(TokenTemplateSeqEnd) | ||
577 | p.PopIncludeNewlines() | ||
578 | continue Token | ||
579 | |||
580 | } | ||
581 | |||
582 | close := p.Peek() | ||
583 | if close.Type != TokenTemplateSeqEnd { | ||
584 | if !p.recovery { | ||
585 | diags = append(diags, &hcl.Diagnostic{ | ||
586 | Severity: hcl.DiagError, | ||
587 | Summary: fmt.Sprintf("Extra characters in %s marker", kw.Bytes), | ||
588 | Detail: "Expected a closing brace to end the sequence, but found extra characters.", | ||
589 | Subject: &close.Range, | ||
590 | Context: hcl.RangeBetween(startRange, close.Range).Ptr(), | ||
591 | }) | ||
592 | } | ||
593 | p.recover(TokenTemplateSeqEnd) | ||
594 | } else { | ||
595 | p.Read() // eat closing brace | ||
596 | |||
597 | // If the closer is ~} then we want to eat any leading | ||
598 | // whitespace on the next token, if it turns out to be a | ||
599 | // literal token. | ||
600 | if len(close.Bytes) == 2 && close.Bytes[0] == '~' { | ||
601 | ltrimNext = true | ||
602 | } | ||
603 | } | ||
604 | p.PopIncludeNewlines() | ||
605 | |||
606 | default: | ||
607 | if !p.recovery { | ||
608 | diags = append(diags, &hcl.Diagnostic{ | ||
609 | Severity: hcl.DiagError, | ||
610 | Summary: "Unterminated template string", | ||
611 | Detail: "No closing marker was found for the string.", | ||
612 | Subject: &next.Range, | ||
613 | Context: hcl.RangeBetween(startRange, next.Range).Ptr(), | ||
614 | }) | ||
615 | } | ||
616 | final := p.recover(end) | ||
617 | endRange = final.Range | ||
618 | break Token | ||
619 | } | ||
620 | } | ||
621 | |||
622 | if len(parts) == 0 { | ||
623 | // If a sequence has no content, we'll treat it as if it had an | ||
624 | // empty string in it because that's what the user probably means | ||
625 | // if they write "" in configuration. | ||
626 | parts = append(parts, &templateLiteralToken{ | ||
627 | Val: "", | ||
628 | SrcRange: hcl.Range{ | ||
629 | // Range is the zero-character span immediately after the | ||
630 | // opening quote. | ||
631 | Filename: startRange.Filename, | ||
632 | Start: startRange.End, | ||
633 | End: startRange.End, | ||
634 | }, | ||
635 | }) | ||
636 | } | ||
637 | |||
638 | // Always end with an end token, so the parser can produce diagnostics | ||
639 | // about unclosed items with proper position information. | ||
640 | parts = append(parts, &templateEndToken{ | ||
641 | SrcRange: endRange, | ||
642 | }) | ||
643 | |||
644 | ret := &templateParts{ | ||
645 | Tokens: parts, | ||
646 | SrcRange: hcl.RangeBetween(startRange, endRange), | ||
647 | } | ||
648 | |||
649 | return ret, diags | ||
650 | } | ||
651 | |||
652 | type templateParts struct { | ||
653 | Tokens []templateToken | ||
654 | SrcRange hcl.Range | ||
655 | } | ||
656 | |||
657 | // templateToken is a higher-level token that represents a single atom within | ||
658 | // the template language. Our template parsing first raises the raw token | ||
659 | // stream to a sequence of templateToken, and then transforms the result into | ||
660 | // an expression tree. | ||
661 | type templateToken interface { | ||
662 | templateToken() templateToken | ||
663 | } | ||
664 | |||
665 | type templateLiteralToken struct { | ||
666 | Val string | ||
667 | SrcRange hcl.Range | ||
668 | isTemplateToken | ||
669 | } | ||
670 | |||
671 | type templateInterpToken struct { | ||
672 | Expr Expression | ||
673 | SrcRange hcl.Range | ||
674 | isTemplateToken | ||
675 | } | ||
676 | |||
677 | type templateIfToken struct { | ||
678 | CondExpr Expression | ||
679 | SrcRange hcl.Range | ||
680 | isTemplateToken | ||
681 | } | ||
682 | |||
683 | type templateForToken struct { | ||
684 | KeyVar string // empty if ignoring key | ||
685 | ValVar string | ||
686 | CollExpr Expression | ||
687 | SrcRange hcl.Range | ||
688 | isTemplateToken | ||
689 | } | ||
690 | |||
691 | type templateEndCtrlType int | ||
692 | |||
693 | const ( | ||
694 | templateEndIf templateEndCtrlType = iota | ||
695 | templateElse | ||
696 | templateEndFor | ||
697 | ) | ||
698 | |||
699 | type templateEndCtrlToken struct { | ||
700 | Type templateEndCtrlType | ||
701 | SrcRange hcl.Range | ||
702 | isTemplateToken | ||
703 | } | ||
704 | |||
705 | func (t *templateEndCtrlToken) Name() string { | ||
706 | switch t.Type { | ||
707 | case templateEndIf: | ||
708 | return "endif" | ||
709 | case templateElse: | ||
710 | return "else" | ||
711 | case templateEndFor: | ||
712 | return "endfor" | ||
713 | default: | ||
714 | // should never happen | ||
715 | panic("invalid templateEndCtrlType") | ||
716 | } | ||
717 | } | ||
718 | |||
719 | type templateEndToken struct { | ||
720 | SrcRange hcl.Range | ||
721 | isTemplateToken | ||
722 | } | ||
723 | |||
724 | type isTemplateToken [0]int | ||
725 | |||
726 | func (t isTemplateToken) templateToken() templateToken { | ||
727 | return t | ||
728 | } | ||