aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser_template.go
diff options
context:
space:
mode:
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.go728
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 @@
1package hclsyntax
2
3import (
4 "fmt"
5 "strings"
6 "unicode"
7
8 "github.com/hashicorp/hcl2/hcl"
9 "github.com/zclconf/go-cty/cty"
10)
11
12func (p *parser) ParseTemplate() (Expression, hcl.Diagnostics) {
13 return p.parseTemplate(TokenEOF)
14}
15
16func (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
35func (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
54type templateParser struct {
55 Tokens []templateToken
56 SrcRange hcl.Range
57
58 pos int
59}
60
61func (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
79func (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
131func (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
144Token:
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
243func (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
255Token:
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
340func (p *templateParser) Peek() templateToken {
341 return p.Tokens[p.pos]
342}
343
344func (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.
357func (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
366Token:
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
652type 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.
661type templateToken interface {
662 templateToken() templateToken
663}
664
665type templateLiteralToken struct {
666 Val string
667 SrcRange hcl.Range
668 isTemplateToken
669}
670
671type templateInterpToken struct {
672 Expr Expression
673 SrcRange hcl.Range
674 isTemplateToken
675}
676
677type templateIfToken struct {
678 CondExpr Expression
679 SrcRange hcl.Range
680 isTemplateToken
681}
682
683type templateForToken struct {
684 KeyVar string // empty if ignoring key
685 ValVar string
686 CollExpr Expression
687 SrcRange hcl.Range
688 isTemplateToken
689}
690
691type templateEndCtrlType int
692
693const (
694 templateEndIf templateEndCtrlType = iota
695 templateElse
696 templateEndFor
697)
698
699type templateEndCtrlToken struct {
700 Type templateEndCtrlType
701 SrcRange hcl.Range
702 isTemplateToken
703}
704
705func (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
719type templateEndToken struct {
720 SrcRange hcl.Range
721 isTemplateToken
722}
723
724type isTemplateToken [0]int
725
726func (t isTemplateToken) templateToken() templateToken {
727 return t
728}