9 "github.com/apparentlymart/go-textseg/textseg"
10 "github.com/hashicorp/hcl2/hcl"
11 "github.com/zclconf/go-cty/cty"
17 // set to true if any recovery is attempted. The parser can use this
18 // to attempt to reduce error noise by suppressing "bad token" errors
19 // in recovery mode, assuming that the recovery heuristics have failed
20 // in this case and left the peeker in a wrong place.
24 func (p *parser) ParseBody(end TokenType) (*Body, hcl.Diagnostics) {
27 var diags hcl.Diagnostics
29 startRange := p.PrevRange()
30 var endRange hcl.Range
36 endRange = p.NextRange()
46 item, itemDiags := p.ParseBodyItem()
47 diags = append(diags, itemDiags...)
48 switch titem := item.(type) {
50 blocks = append(blocks, titem)
52 if existing, exists := attrs[titem.Name]; exists {
53 diags = append(diags, &hcl.Diagnostic{
54 Severity: hcl.DiagError,
55 Summary: "Attribute redefined",
57 "The argument %q was already set at %s. Each argument may be set only once.",
58 titem.Name, existing.NameRange.String(),
60 Subject: &titem.NameRange,
63 attrs[titem.Name] = titem
66 // This should never happen for valid input, but may if a
67 // syntax error was detected in ParseBodyItem that prevented
68 // it from even producing a partially-broken item. In that
69 // case, it would've left at least one error in the diagnostics
70 // slice we already dealt with above.
72 // We'll assume ParseBodyItem attempted recovery to leave
73 // us in a reasonable position to try parsing the next item.
79 if bad.Type == TokenOQuote {
80 diags = append(diags, &hcl.Diagnostic{
81 Severity: hcl.DiagError,
82 Summary: "Invalid argument name",
83 Detail: "Argument names must not be quoted.",
87 diags = append(diags, &hcl.Diagnostic{
88 Severity: hcl.DiagError,
89 Summary: "Argument or block definition required",
90 Detail: "An argument or block definition is required here.",
95 endRange = p.PrevRange() // arbitrary, but somewhere inside the body means better diagnostics
97 p.recover(end) // attempt to recover to the token after the end of this body
106 SrcRange: hcl.RangeBetween(startRange, endRange),
108 Filename: endRange.Filename,
115 func (p *parser) ParseBodyItem() (Node, hcl.Diagnostics) {
117 if ident.Type != TokenIdent {
118 p.recoverAfterBodyItem()
119 return nil, hcl.Diagnostics{
121 Severity: hcl.DiagError,
122 Summary: "Argument or block definition required",
123 Detail: "An argument or block definition is required here.",
124 Subject: &ident.Range,
133 return p.finishParsingBodyAttribute(ident, false)
134 case TokenOQuote, TokenOBrace, TokenIdent:
135 return p.finishParsingBodyBlock(ident)
137 p.recoverAfterBodyItem()
138 return nil, hcl.Diagnostics{
140 Severity: hcl.DiagError,
141 Summary: "Argument or block definition required",
142 Detail: "An argument or block definition is required here. To set an argument, use the equals sign \"=\" to introduce the argument value.",
143 Subject: &ident.Range,
151 // parseSingleAttrBody is a weird variant of ParseBody that deals with the
152 // body of a nested block containing only one attribute value all on a single
153 // line, like foo { bar = baz } . It expects to find a single attribute item
154 // immediately followed by the end token type with no intervening newlines.
155 func (p *parser) parseSingleAttrBody(end TokenType) (*Body, hcl.Diagnostics) {
157 if ident.Type != TokenIdent {
158 p.recoverAfterBodyItem()
159 return nil, hcl.Diagnostics{
161 Severity: hcl.DiagError,
162 Summary: "Argument or block definition required",
163 Detail: "An argument or block definition is required here.",
164 Subject: &ident.Range,
170 var diags hcl.Diagnostics
176 node, attrDiags := p.finishParsingBodyAttribute(ident, true)
177 diags = append(diags, attrDiags...)
178 attr = node.(*Attribute)
179 case TokenOQuote, TokenOBrace, TokenIdent:
180 p.recoverAfterBodyItem()
181 return nil, hcl.Diagnostics{
183 Severity: hcl.DiagError,
184 Summary: "Argument definition required",
185 Detail: fmt.Sprintf("A single-line block definition can contain only a single argument. If you meant to define argument %q, use an equals sign to assign it a value. To define a nested block, place it on a line of its own within its parent block.", ident.Bytes),
186 Subject: hcl.RangeBetween(ident.Range, next.Range).Ptr(),
190 p.recoverAfterBodyItem()
191 return nil, hcl.Diagnostics{
193 Severity: hcl.DiagError,
194 Summary: "Argument or block definition required",
195 Detail: "An argument or block definition is required here. To set an argument, use the equals sign \"=\" to introduce the argument value.",
196 Subject: &ident.Range,
202 Attributes: Attributes{
203 string(ident.Bytes): attr,
206 SrcRange: attr.SrcRange,
208 Filename: attr.SrcRange.Filename,
209 Start: attr.SrcRange.End,
210 End: attr.SrcRange.End,
216 func (p *parser) finishParsingBodyAttribute(ident Token, singleLine bool) (Node, hcl.Diagnostics) {
217 eqTok := p.Read() // eat equals token
218 if eqTok.Type != TokenEqual {
219 // should never happen if caller behaves
220 panic("finishParsingBodyAttribute called with next not equals")
223 var endRange hcl.Range
225 expr, diags := p.ParseExpression()
226 if p.recovery && diags.HasErrors() {
227 // recovery within expressions tends to be tricky, so we've probably
228 // landed somewhere weird. We'll try to reset to the start of a body
229 // item so parsing can continue.
230 endRange = p.PrevRange()
231 p.recoverAfterBodyItem()
233 endRange = p.PrevRange()
236 if end.Type != TokenNewline && end.Type != TokenEOF {
238 summary := "Missing newline after argument"
239 detail := "An argument definition must end with a newline."
241 if end.Type == TokenComma {
242 summary = "Unexpected comma after argument"
243 detail = "Argument definitions must be separated by newlines, not commas. " + detail
246 diags = append(diags, &hcl.Diagnostic{
247 Severity: hcl.DiagError,
251 Context: hcl.RangeBetween(ident.Range, end.Range).Ptr(),
254 endRange = p.PrevRange()
255 p.recoverAfterBodyItem()
257 endRange = p.PrevRange()
258 p.Read() // eat newline
264 Name: string(ident.Bytes),
267 SrcRange: hcl.RangeBetween(ident.Range, endRange),
268 NameRange: ident.Range,
269 EqualsRange: eqTok.Range,
273 func (p *parser) finishParsingBodyBlock(ident Token) (Node, hcl.Diagnostics) {
274 var blockType = string(ident.Bytes)
275 var diags hcl.Diagnostics
277 var labelRanges []hcl.Range
292 label, labelRange, labelDiags := p.parseQuotedStringLiteral()
293 diags = append(diags, labelDiags...)
294 labels = append(labels, label)
295 labelRanges = append(labelRanges, labelRange)
296 // parseQuoteStringLiteral recovers up to the closing quote
297 // if it encounters problems, so we can continue looking for
298 // more labels and eventually the block body even.
301 tok = p.Read() // eat token
302 label, labelRange := string(tok.Bytes), tok.Range
303 labels = append(labels, label)
304 labelRanges = append(labelRanges, labelRange)
309 diags = append(diags, &hcl.Diagnostic{
310 Severity: hcl.DiagError,
311 Summary: "Invalid block definition",
312 Detail: "The equals sign \"=\" indicates an argument definition, and must not be used when defining a block.",
314 Context: hcl.RangeBetween(ident.Range, tok.Range).Ptr(),
317 diags = append(diags, &hcl.Diagnostic{
318 Severity: hcl.DiagError,
319 Summary: "Invalid block definition",
320 Detail: "A block definition must have block content delimited by \"{\" and \"}\", starting on the same line as the block header.",
322 Context: hcl.RangeBetween(ident.Range, tok.Range).Ptr(),
326 diags = append(diags, &hcl.Diagnostic{
327 Severity: hcl.DiagError,
328 Summary: "Invalid block definition",
329 Detail: "Either a quoted string block label or an opening brace (\"{\") is expected here.",
331 Context: hcl.RangeBetween(ident.Range, tok.Range).Ptr(),
336 p.recoverAfterBodyItem()
342 SrcRange: ident.Range,
343 EndRange: ident.Range,
346 TypeRange: ident.Range,
347 LabelRanges: labelRanges,
348 OpenBraceRange: ident.Range, // placeholder
349 CloseBraceRange: ident.Range, // placeholder
354 // Once we fall out here, the peeker is pointed just after our opening
355 // brace, so we can begin our nested body parsing.
357 var bodyDiags hcl.Diagnostics
358 switch p.Peek().Type {
359 case TokenNewline, TokenEOF, TokenCBrace:
360 body, bodyDiags = p.ParseBody(TokenCBrace)
362 // Special one-line, single-attribute block parsing mode.
363 body, bodyDiags = p.parseSingleAttrBody(TokenCBrace)
364 switch p.Peek().Type {
366 p.Read() // the happy path - just consume the closing brace
368 // User seems to be trying to use the object-constructor
369 // comma-separated style, which isn't permitted for blocks.
370 diags = append(diags, &hcl.Diagnostic{
371 Severity: hcl.DiagError,
372 Summary: "Invalid single-argument block definition",
373 Detail: "Single-line block syntax can include only one argument definition. To define multiple arguments, use the multi-line block syntax with one argument definition per line.",
374 Subject: p.Peek().Range.Ptr(),
376 p.recover(TokenCBrace)
378 // We don't allow weird mixtures of single and multi-line syntax.
379 diags = append(diags, &hcl.Diagnostic{
380 Severity: hcl.DiagError,
381 Summary: "Invalid single-argument block definition",
382 Detail: "An argument definition on the same line as its containing block creates a single-line block definition, which must also be closed on the same line. Place the block's closing brace immediately after the argument definition.",
383 Subject: p.Peek().Range.Ptr(),
385 p.recover(TokenCBrace)
387 // Some other weird thing is going on. Since we can't guess a likely
388 // user intent for this one, we'll skip it if we're already in
391 diags = append(diags, &hcl.Diagnostic{
392 Severity: hcl.DiagError,
393 Summary: "Invalid single-argument block definition",
394 Detail: "A single-line block definition must end with a closing brace immediately after its single argument definition.",
395 Subject: p.Peek().Range.Ptr(),
398 p.recover(TokenCBrace)
401 diags = append(diags, bodyDiags...)
402 cBraceRange := p.PrevRange()
405 if eol.Type == TokenNewline || eol.Type == TokenEOF {
406 p.Read() // eat newline
409 diags = append(diags, &hcl.Diagnostic{
410 Severity: hcl.DiagError,
411 Summary: "Missing newline after block definition",
412 Detail: "A block definition must end with a newline.",
414 Context: hcl.RangeBetween(ident.Range, eol.Range).Ptr(),
417 p.recoverAfterBodyItem()
420 // We must never produce a nil body, since the caller may attempt to
421 // do analysis of a partial result when there's an error, so we'll
422 // insert a placeholder if we otherwise failed to produce a valid
423 // body due to one of the syntax error paths above.
424 if body == nil && diags.HasErrors() {
426 SrcRange: hcl.RangeBetween(oBrace.Range, cBraceRange),
427 EndRange: cBraceRange,
436 TypeRange: ident.Range,
437 LabelRanges: labelRanges,
438 OpenBraceRange: oBrace.Range,
439 CloseBraceRange: cBraceRange,
443 func (p *parser) ParseExpression() (Expression, hcl.Diagnostics) {
444 return p.parseTernaryConditional()
447 func (p *parser) parseTernaryConditional() (Expression, hcl.Diagnostics) {
448 // The ternary conditional operator (.. ? .. : ..) behaves somewhat
449 // like a binary operator except that the "symbol" is itself
450 // an expression enclosed in two punctuation characters.
451 // The middle expression is parsed as if the ? and : symbols
452 // were parentheses. The "rhs" (the "false expression") is then
453 // treated right-associatively so it behaves similarly to the
454 // middle in terms of precedence.
456 startRange := p.NextRange()
457 var condExpr, trueExpr, falseExpr Expression
458 var diags hcl.Diagnostics
460 condExpr, condDiags := p.parseBinaryOps(binaryOps)
461 diags = append(diags, condDiags...)
462 if p.recovery && condDiags.HasErrors() {
463 return condExpr, diags
466 questionMark := p.Peek()
467 if questionMark.Type != TokenQuestion {
468 return condExpr, diags
471 p.Read() // eat question mark
473 trueExpr, trueDiags := p.ParseExpression()
474 diags = append(diags, trueDiags...)
475 if p.recovery && trueDiags.HasErrors() {
476 return condExpr, diags
480 if colon.Type != TokenColon {
481 diags = append(diags, &hcl.Diagnostic{
482 Severity: hcl.DiagError,
483 Summary: "Missing false expression in conditional",
484 Detail: "The conditional operator (...?...:...) requires a false expression, delimited by a colon.",
485 Subject: &colon.Range,
486 Context: hcl.RangeBetween(startRange, colon.Range).Ptr(),
488 return condExpr, diags
491 p.Read() // eat colon
493 falseExpr, falseDiags := p.ParseExpression()
494 diags = append(diags, falseDiags...)
495 if p.recovery && falseDiags.HasErrors() {
496 return condExpr, diags
499 return &ConditionalExpr{
501 TrueResult: trueExpr,
502 FalseResult: falseExpr,
504 SrcRange: hcl.RangeBetween(startRange, falseExpr.Range()),
508 // parseBinaryOps calls itself recursively to work through all of the
509 // operator precedence groups, and then eventually calls parseExpressionTerm
511 func (p *parser) parseBinaryOps(ops []map[TokenType]*Operation) (Expression, hcl.Diagnostics) {
513 // We've run out of operators, so now we'll just try to parse a term.
514 return p.parseExpressionWithTraversals()
520 var lhs, rhs Expression
521 var operation *Operation
522 var diags hcl.Diagnostics
524 // Parse a term that might be the first operand of a binary
525 // operation or it might just be a standalone term.
526 // We won't know until we've parsed it and can look ahead
527 // to see if there's an operator token for this level.
528 lhs, lhsDiags := p.parseBinaryOps(remaining)
529 diags = append(diags, lhsDiags...)
530 if p.recovery && lhsDiags.HasErrors() {
534 // We'll keep eating up operators until we run out, so that operators
535 // with the same precedence will combine in a left-associative manner:
536 // a+b+c => (a+b)+c, not a+(b+c)
538 // Should we later want to have right-associative operators, a way
539 // to achieve that would be to call back up to ParseExpression here
540 // instead of iteratively parsing only the remaining operators.
545 if newOp, ok = thisLevel[next.Type]; !ok {
549 // Are we extending an expression started on the previous iteration?
550 if operation != nil {
556 SrcRange: hcl.RangeBetween(lhs.Range(), rhs.Range()),
561 p.Read() // eat operator token
562 var rhsDiags hcl.Diagnostics
563 rhs, rhsDiags = p.parseBinaryOps(remaining)
564 diags = append(diags, rhsDiags...)
565 if p.recovery && rhsDiags.HasErrors() {
570 if operation == nil {
574 return &BinaryOpExpr{
579 SrcRange: hcl.RangeBetween(lhs.Range(), rhs.Range()),
583 func (p *parser) parseExpressionWithTraversals() (Expression, hcl.Diagnostics) {
584 term, diags := p.parseExpressionTerm()
585 ret, moreDiags := p.parseExpressionTraversals(term)
586 diags = append(diags, moreDiags...)
590 func (p *parser) parseExpressionTraversals(from Expression) (Expression, hcl.Diagnostics) {
591 var diags hcl.Diagnostics
600 // Attribute access or splat
604 switch attrTok.Type {
606 attrTok = p.Read() // eat token
607 name := string(attrTok.Bytes)
608 rng := hcl.RangeBetween(dot.Range, attrTok.Range)
609 step := hcl.TraverseAttr{
614 ret = makeRelativeTraversal(ret, step, rng)
617 // This is a weird form we inherited from HIL, allowing numbers
618 // to be used as attributes as a weird way of writing [n].
619 // This was never actually a first-class thing in HIL, but
620 // HIL tolerated sequences like .0. in its variable names and
621 // calling applications like Terraform exploited that to
622 // introduce indexing syntax where none existed.
623 numTok := p.Read() // eat token
626 // This syntax is ambiguous if multiple indices are used in
627 // succession, like foo.0.1.baz: that actually parses as
628 // a fractional number 0.1. Since we're only supporting this
629 // syntax for compatibility with legacy Terraform
630 // configurations, and Terraform does not tend to have lists
631 // of lists, we'll choose to reject that here with a helpful
632 // error message, rather than failing later because the index
633 // isn't a whole number.
634 if dotIdx := bytes.IndexByte(numTok.Bytes, '.'); dotIdx >= 0 {
635 first := numTok.Bytes[:dotIdx]
636 second := numTok.Bytes[dotIdx+1:]
637 diags = append(diags, &hcl.Diagnostic{
638 Severity: hcl.DiagError,
639 Summary: "Invalid legacy index syntax",
640 Detail: fmt.Sprintf("When using the legacy index syntax, chaining two indexes together is not permitted. Use the proper index syntax instead, like [%s][%s].", first, second),
641 Subject: &attrTok.Range,
643 rng := hcl.RangeBetween(dot.Range, numTok.Range)
644 step := hcl.TraverseIndex{
648 ret = makeRelativeTraversal(ret, step, rng)
652 numVal, numDiags := p.numberLitValue(numTok)
653 diags = append(diags, numDiags...)
655 rng := hcl.RangeBetween(dot.Range, numTok.Range)
656 step := hcl.TraverseIndex{
661 ret = makeRelativeTraversal(ret, step, rng)
664 // "Attribute-only" splat expression.
665 // (This is a kinda weird construct inherited from HIL, which
666 // behaves a bit like a [*] splat except that it is only able
667 // to do attribute traversals into each of its elements,
668 // whereas foo[*] can support _any_ traversal.
669 marker := p.Read() // eat star
670 trav := make(hcl.Traversal, 0, 1)
671 var firstRange, lastRange hcl.Range
672 firstRange = p.NextRange()
673 for p.Peek().Type == TokenDot {
676 if p.Peek().Type == TokenNumberLit {
677 // Continuing the "weird stuff inherited from HIL"
678 // theme, we also allow numbers as attribute names
679 // inside splats and interpret them as indexing
680 // into a list, for expressions like:
681 // foo.bar.*.baz.0.foo
684 // Weird special case if the user writes something
685 // like foo.bar.*.baz.0.0.foo, where 0.0 parses
687 if dotIdx := bytes.IndexByte(numTok.Bytes, '.'); dotIdx >= 0 {
688 first := numTok.Bytes[:dotIdx]
689 second := numTok.Bytes[dotIdx+1:]
690 diags = append(diags, &hcl.Diagnostic{
691 Severity: hcl.DiagError,
692 Summary: "Invalid legacy index syntax",
693 Detail: fmt.Sprintf("When using the legacy index syntax, chaining two indexes together is not permitted. Use the proper index syntax with a full splat expression [*] instead, like [%s][%s].", first, second),
694 Subject: &attrTok.Range,
696 trav = append(trav, hcl.TraverseIndex{
698 SrcRange: hcl.RangeBetween(dot.Range, numTok.Range),
700 lastRange = numTok.Range
704 numVal, numDiags := p.numberLitValue(numTok)
705 diags = append(diags, numDiags...)
706 trav = append(trav, hcl.TraverseIndex{
708 SrcRange: hcl.RangeBetween(dot.Range, numTok.Range),
710 lastRange = numTok.Range
714 if p.Peek().Type != TokenIdent {
716 if p.Peek().Type == TokenStar {
717 diags = append(diags, &hcl.Diagnostic{
718 Severity: hcl.DiagError,
719 Summary: "Nested splat expression not allowed",
720 Detail: "A splat expression (*) cannot be used inside another attribute-only splat expression.",
721 Subject: p.Peek().Range.Ptr(),
724 diags = append(diags, &hcl.Diagnostic{
725 Severity: hcl.DiagError,
726 Summary: "Invalid attribute name",
727 Detail: "An attribute name is required after a dot.",
728 Subject: &attrTok.Range,
737 trav = append(trav, hcl.TraverseAttr{
738 Name: string(attrTok.Bytes),
739 SrcRange: hcl.RangeBetween(dot.Range, attrTok.Range),
741 lastRange = attrTok.Range
744 itemExpr := &AnonSymbolExpr{
745 SrcRange: hcl.RangeBetween(dot.Range, marker.Range),
747 var travExpr Expression
751 travExpr = &RelativeTraversalExpr{
754 SrcRange: hcl.RangeBetween(firstRange, lastRange),
763 SrcRange: hcl.RangeBetween(dot.Range, lastRange),
764 MarkerRange: hcl.RangeBetween(dot.Range, marker.Range),
768 diags = append(diags, &hcl.Diagnostic{
769 Severity: hcl.DiagError,
770 Summary: "Invalid attribute name",
771 Detail: "An attribute name is required after a dot.",
772 Subject: &attrTok.Range,
774 // This leaves the peeker in a bad place, so following items
775 // will probably be misparsed until we hit something that
776 // allows us to re-sync.
778 // We will probably need to do something better here eventually
779 // in order to support autocomplete triggered by typing a
785 // Indexing of a collection.
786 // This may or may not be a hcl.Traverser, depending on whether
787 // the key value is something constant.
790 switch p.Peek().Type {
792 // This is a full splat expression, like foo[*], which consumes
793 // the rest of the traversal steps after it using a recursive
794 // call to this function.
795 p.Read() // consume star
797 if close.Type != TokenCBrack && !p.recovery {
798 diags = append(diags, &hcl.Diagnostic{
799 Severity: hcl.DiagError,
800 Summary: "Missing close bracket on splat index",
801 Detail: "The star for a full splat operator must be immediately followed by a closing bracket (\"]\").",
802 Subject: &close.Range,
804 close = p.recover(TokenCBrack)
806 // Splat expressions use a special "anonymous symbol" as a
807 // placeholder in an expression to be evaluated once for each
808 // item in the source expression.
809 itemExpr := &AnonSymbolExpr{
810 SrcRange: hcl.RangeBetween(open.Range, close.Range),
812 // Now we'll recursively call this same function to eat any
813 // remaining traversal steps against the anonymous symbol.
814 travExpr, nestedDiags := p.parseExpressionTraversals(itemExpr)
815 diags = append(diags, nestedDiags...)
822 SrcRange: hcl.RangeBetween(open.Range, travExpr.Range()),
823 MarkerRange: hcl.RangeBetween(open.Range, close.Range),
829 p.PushIncludeNewlines(false) // arbitrary newlines allowed in brackets
830 keyExpr, keyDiags := p.ParseExpression()
831 diags = append(diags, keyDiags...)
832 if p.recovery && keyDiags.HasErrors() {
833 close = p.recover(TokenCBrack)
836 if close.Type != TokenCBrack && !p.recovery {
837 diags = append(diags, &hcl.Diagnostic{
838 Severity: hcl.DiagError,
839 Summary: "Missing close bracket on index",
840 Detail: "The index operator must end with a closing bracket (\"]\").",
841 Subject: &close.Range,
843 close = p.recover(TokenCBrack)
846 p.PopIncludeNewlines()
848 if lit, isLit := keyExpr.(*LiteralValueExpr); isLit {
849 litKey, _ := lit.Value(nil)
850 rng := hcl.RangeBetween(open.Range, close.Range)
851 step := hcl.TraverseIndex{
855 ret = makeRelativeTraversal(ret, step, rng)
856 } else if tmpl, isTmpl := keyExpr.(*TemplateExpr); isTmpl && tmpl.IsStringLiteral() {
857 litKey, _ := tmpl.Value(nil)
858 rng := hcl.RangeBetween(open.Range, close.Range)
859 step := hcl.TraverseIndex{
863 ret = makeRelativeTraversal(ret, step, rng)
865 rng := hcl.RangeBetween(open.Range, close.Range)
871 OpenRange: open.Range,
884 // makeRelativeTraversal takes an expression and a traverser and returns
885 // a traversal expression that combines the two. If the given expression
886 // is already a traversal, it is extended in place (mutating it) and
887 // returned. If it isn't, a new RelativeTraversalExpr is created and returned.
888 func makeRelativeTraversal(expr Expression, next hcl.Traverser, rng hcl.Range) Expression {
889 switch texpr := expr.(type) {
890 case *ScopeTraversalExpr:
891 texpr.Traversal = append(texpr.Traversal, next)
892 texpr.SrcRange = hcl.RangeBetween(texpr.SrcRange, rng)
894 case *RelativeTraversalExpr:
895 texpr.Traversal = append(texpr.Traversal, next)
896 texpr.SrcRange = hcl.RangeBetween(texpr.SrcRange, rng)
899 return &RelativeTraversalExpr{
901 Traversal: hcl.Traversal{next},
907 func (p *parser) parseExpressionTerm() (Expression, hcl.Diagnostics) {
912 p.Read() // eat open paren
914 p.PushIncludeNewlines(false)
916 expr, diags := p.ParseExpression()
917 if diags.HasErrors() {
918 // attempt to place the peeker after our closing paren
919 // before we return, so that the next parser has some
920 // chance of finding a valid expression.
921 p.recover(TokenCParen)
922 p.PopIncludeNewlines()
927 if close.Type != TokenCParen {
928 diags = append(diags, &hcl.Diagnostic{
929 Severity: hcl.DiagError,
930 Summary: "Unbalanced parentheses",
931 Detail: "Expected a closing parenthesis to terminate the expression.",
932 Subject: &close.Range,
933 Context: hcl.RangeBetween(start.Range, close.Range).Ptr(),
938 p.Read() // eat closing paren
939 p.PopIncludeNewlines()
944 tok := p.Read() // eat number token
946 numVal, diags := p.numberLitValue(tok)
947 return &LiteralValueExpr{
953 tok := p.Read() // eat identifier token
955 if p.Peek().Type == TokenOParen {
956 return p.finishParsingFunctionCall(tok)
959 name := string(tok.Bytes)
962 return &LiteralValueExpr{
967 return &LiteralValueExpr{
972 return &LiteralValueExpr{
973 Val: cty.NullVal(cty.DynamicPseudoType),
977 return &ScopeTraversalExpr{
978 Traversal: hcl.Traversal{
988 case TokenOQuote, TokenOHeredoc:
989 open := p.Read() // eat opening marker
990 closer := p.oppositeBracket(open.Type)
991 exprs, passthru, _, diags := p.parseTemplateInner(closer, tokenOpensFlushHeredoc(open))
993 closeRange := p.PrevRange()
997 panic("passthru set with len(exprs) != 1")
999 return &TemplateWrapExpr{
1001 SrcRange: hcl.RangeBetween(open.Range, closeRange),
1005 return &TemplateExpr{
1007 SrcRange: hcl.RangeBetween(open.Range, closeRange),
1011 tok := p.Read() // eat minus token
1013 // Important to use parseExpressionWithTraversals rather than parseExpression
1014 // here, otherwise we can capture a following binary expression into
1016 // e.g. -46+5 should parse as (-46)+5, not -(46+5)
1017 operand, diags := p.parseExpressionWithTraversals()
1018 return &UnaryOpExpr{
1022 SrcRange: hcl.RangeBetween(tok.Range, operand.Range()),
1023 SymbolRange: tok.Range,
1027 tok := p.Read() // eat bang token
1029 // Important to use parseExpressionWithTraversals rather than parseExpression
1030 // here, otherwise we can capture a following binary expression into
1032 operand, diags := p.parseExpressionWithTraversals()
1033 return &UnaryOpExpr{
1037 SrcRange: hcl.RangeBetween(tok.Range, operand.Range()),
1038 SymbolRange: tok.Range,
1042 return p.parseTupleCons()
1045 return p.parseObjectCons()
1048 var diags hcl.Diagnostics
1050 diags = append(diags, &hcl.Diagnostic{
1051 Severity: hcl.DiagError,
1052 Summary: "Invalid expression",
1053 Detail: "Expected the start of an expression, but found an invalid expression token.",
1054 Subject: &start.Range,
1059 // Return a placeholder so that the AST is still structurally sound
1060 // even in the presence of parse errors.
1061 return &LiteralValueExpr{
1062 Val: cty.DynamicVal,
1063 SrcRange: start.Range,
1068 func (p *parser) numberLitValue(tok Token) (cty.Value, hcl.Diagnostics) {
1069 // The cty.ParseNumberVal is always the same behavior as converting a
1070 // string to a number, ensuring we always interpret decimal numbers in
1072 numVal, err := cty.ParseNumberVal(string(tok.Bytes))
1074 ret := cty.UnknownVal(cty.Number)
1075 return ret, hcl.Diagnostics{
1077 Severity: hcl.DiagError,
1078 Summary: "Invalid number literal",
1079 // FIXME: not a very good error message, but convert only
1080 // gives us "a number is required", so not much help either.
1081 Detail: "Failed to recognize the value of this number literal.",
1082 Subject: &tok.Range,
1089 // finishParsingFunctionCall parses a function call assuming that the function
1090 // name was already read, and so the peeker should be pointing at the opening
1091 // parenthesis after the name.
1092 func (p *parser) finishParsingFunctionCall(name Token) (Expression, hcl.Diagnostics) {
1094 if openTok.Type != TokenOParen {
1095 // should never happen if callers behave
1096 panic("finishParsingFunctionCall called with non-parenthesis as next token")
1099 var args []Expression
1100 var diags hcl.Diagnostics
1101 var expandFinal bool
1104 // Arbitrary newlines are allowed inside the function call parentheses.
1105 p.PushIncludeNewlines(false)
1111 if tok.Type == TokenCParen {
1112 closeTok = p.Read() // eat closing paren
1116 arg, argDiags := p.ParseExpression()
1117 args = append(args, arg)
1118 diags = append(diags, argDiags...)
1119 if p.recovery && argDiags.HasErrors() {
1120 // if there was a parse error in the argument then we've
1121 // probably been left in a weird place in the token stream,
1122 // so we'll bail out with a partial argument list.
1123 p.recover(TokenCParen)
1128 if sep.Type == TokenCParen {
1133 if sep.Type == TokenEllipsis {
1136 if p.Peek().Type != TokenCParen {
1138 diags = append(diags, &hcl.Diagnostic{
1139 Severity: hcl.DiagError,
1140 Summary: "Missing closing parenthesis",
1141 Detail: "An expanded function argument (with ...) must be immediately followed by closing parentheses.",
1142 Subject: &sep.Range,
1143 Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(),
1146 closeTok = p.recover(TokenCParen)
1148 closeTok = p.Read() // eat closing paren
1153 if sep.Type != TokenComma {
1154 diags = append(diags, &hcl.Diagnostic{
1155 Severity: hcl.DiagError,
1156 Summary: "Missing argument separator",
1157 Detail: "A comma is required to separate each function argument from the next.",
1158 Subject: &sep.Range,
1159 Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(),
1161 closeTok = p.recover(TokenCParen)
1165 if p.Peek().Type == TokenCParen {
1166 // A trailing comma after the last argument gets us in here.
1167 closeTok = p.Read() // eat closing paren
1173 p.PopIncludeNewlines()
1175 return &FunctionCallExpr{
1176 Name: string(name.Bytes),
1179 ExpandFinal: expandFinal,
1181 NameRange: name.Range,
1182 OpenParenRange: openTok.Range,
1183 CloseParenRange: closeTok.Range,
1187 func (p *parser) parseTupleCons() (Expression, hcl.Diagnostics) {
1189 if open.Type != TokenOBrack {
1190 // Should never happen if callers are behaving
1191 panic("parseTupleCons called without peeker pointing to open bracket")
1194 p.PushIncludeNewlines(false)
1195 defer p.PopIncludeNewlines()
1197 if forKeyword.TokenMatches(p.Peek()) {
1198 return p.finishParsingForExpr(open)
1203 var diags hcl.Diagnostics
1204 var exprs []Expression
1208 if next.Type == TokenCBrack {
1209 close = p.Read() // eat closer
1213 expr, exprDiags := p.ParseExpression()
1214 exprs = append(exprs, expr)
1215 diags = append(diags, exprDiags...)
1217 if p.recovery && exprDiags.HasErrors() {
1218 // If expression parsing failed then we are probably in a strange
1219 // place in the token stream, so we'll bail out and try to reset
1220 // to after our closing bracket to allow parsing to continue.
1221 close = p.recover(TokenCBrack)
1226 if next.Type == TokenCBrack {
1227 close = p.Read() // eat closer
1231 if next.Type != TokenComma {
1233 diags = append(diags, &hcl.Diagnostic{
1234 Severity: hcl.DiagError,
1235 Summary: "Missing item separator",
1236 Detail: "Expected a comma to mark the beginning of the next item.",
1237 Subject: &next.Range,
1238 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
1241 close = p.recover(TokenCBrack)
1245 p.Read() // eat comma
1249 return &TupleConsExpr{
1252 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1253 OpenRange: open.Range,
1257 func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) {
1259 if open.Type != TokenOBrace {
1260 // Should never happen if callers are behaving
1261 panic("parseObjectCons called without peeker pointing to open brace")
1264 // We must temporarily stop looking at newlines here while we check for
1265 // a "for" keyword, since for expressions are _not_ newline-sensitive,
1266 // even though object constructors are.
1267 p.PushIncludeNewlines(false)
1268 isFor := forKeyword.TokenMatches(p.Peek())
1269 p.PopIncludeNewlines()
1271 return p.finishParsingForExpr(open)
1274 p.PushIncludeNewlines(true)
1275 defer p.PopIncludeNewlines()
1279 var diags hcl.Diagnostics
1280 var items []ObjectConsItem
1284 if next.Type == TokenNewline {
1285 p.Read() // eat newline
1289 if next.Type == TokenCBrace {
1290 close = p.Read() // eat closer
1295 var keyDiags hcl.Diagnostics
1296 key, keyDiags = p.ParseExpression()
1297 diags = append(diags, keyDiags...)
1299 if p.recovery && keyDiags.HasErrors() {
1300 // If expression parsing failed then we are probably in a strange
1301 // place in the token stream, so we'll bail out and try to reset
1302 // to after our closing brace to allow parsing to continue.
1303 close = p.recover(TokenCBrace)
1307 // We wrap up the key expression in a special wrapper that deals
1308 // with our special case that naked identifiers as object keys
1309 // are interpreted as literal strings.
1310 key = &ObjectConsKeyExpr{Wrapped: key}
1313 if next.Type != TokenEqual && next.Type != TokenColon {
1316 case TokenNewline, TokenComma:
1317 diags = append(diags, &hcl.Diagnostic{
1318 Severity: hcl.DiagError,
1319 Summary: "Missing attribute value",
1320 Detail: "Expected an attribute value, introduced by an equals sign (\"=\").",
1321 Subject: &next.Range,
1322 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
1325 // Although this might just be a plain old missing equals
1326 // sign before a reference, one way to get here is to try
1327 // to write an attribute name containing a period followed
1328 // by a digit, which was valid in HCL1, like this:
1329 // foo1.2_bar = "baz"
1330 // We can't know exactly what the user intended here, but
1331 // we'll augment our message with an extra hint in this case
1332 // in case it is helpful.
1333 diags = append(diags, &hcl.Diagnostic{
1334 Severity: hcl.DiagError,
1335 Summary: "Missing key/value separator",
1336 Detail: "Expected an equals sign (\"=\") to mark the beginning of the attribute value. If you intended to given an attribute name containing periods or spaces, write the name in quotes to create a string literal.",
1337 Subject: &next.Range,
1338 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
1341 diags = append(diags, &hcl.Diagnostic{
1342 Severity: hcl.DiagError,
1343 Summary: "Missing key/value separator",
1344 Detail: "Expected an equals sign (\"=\") to mark the beginning of the attribute value.",
1345 Subject: &next.Range,
1346 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
1350 close = p.recover(TokenCBrace)
1354 p.Read() // eat equals sign or colon
1356 value, valueDiags := p.ParseExpression()
1357 diags = append(diags, valueDiags...)
1359 if p.recovery && valueDiags.HasErrors() {
1360 // If expression parsing failed then we are probably in a strange
1361 // place in the token stream, so we'll bail out and try to reset
1362 // to after our closing brace to allow parsing to continue.
1363 close = p.recover(TokenCBrace)
1367 items = append(items, ObjectConsItem{
1373 if next.Type == TokenCBrace {
1374 close = p.Read() // eat closer
1378 if next.Type != TokenComma && next.Type != TokenNewline {
1380 diags = append(diags, &hcl.Diagnostic{
1381 Severity: hcl.DiagError,
1382 Summary: "Missing attribute separator",
1383 Detail: "Expected a newline or comma to mark the beginning of the next attribute.",
1384 Subject: &next.Range,
1385 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
1388 close = p.recover(TokenCBrace)
1392 p.Read() // eat comma or newline
1396 return &ObjectConsExpr{
1399 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1400 OpenRange: open.Range,
1404 func (p *parser) finishParsingForExpr(open Token) (Expression, hcl.Diagnostics) {
1405 p.PushIncludeNewlines(false)
1406 defer p.PopIncludeNewlines()
1407 introducer := p.Read()
1408 if !forKeyword.TokenMatches(introducer) {
1409 // Should never happen if callers are behaving
1410 panic("finishParsingForExpr called without peeker pointing to 'for' identifier")
1414 var closeType TokenType
1418 closeType = TokenCBrace
1420 makeObj = false // making a tuple
1421 closeType = TokenCBrack
1423 // Should never happen if callers are behaving
1424 panic("finishParsingForExpr called with invalid open token")
1427 var diags hcl.Diagnostics
1428 var keyName, valName string
1430 if p.Peek().Type != TokenIdent {
1432 diags = append(diags, &hcl.Diagnostic{
1433 Severity: hcl.DiagError,
1434 Summary: "Invalid 'for' expression",
1435 Detail: "For expression requires variable name after 'for'.",
1436 Subject: p.Peek().Range.Ptr(),
1437 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1440 close := p.recover(closeType)
1441 return &LiteralValueExpr{
1442 Val: cty.DynamicVal,
1443 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1447 valName = string(p.Read().Bytes)
1449 if p.Peek().Type == TokenComma {
1450 // What we just read was actually the key, then.
1452 p.Read() // eat comma
1454 if p.Peek().Type != TokenIdent {
1456 diags = append(diags, &hcl.Diagnostic{
1457 Severity: hcl.DiagError,
1458 Summary: "Invalid 'for' expression",
1459 Detail: "For expression requires value variable name after comma.",
1460 Subject: p.Peek().Range.Ptr(),
1461 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1464 close := p.recover(closeType)
1465 return &LiteralValueExpr{
1466 Val: cty.DynamicVal,
1467 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1471 valName = string(p.Read().Bytes)
1474 if !inKeyword.TokenMatches(p.Peek()) {
1476 diags = append(diags, &hcl.Diagnostic{
1477 Severity: hcl.DiagError,
1478 Summary: "Invalid 'for' expression",
1479 Detail: "For expression requires the 'in' keyword after its name declarations.",
1480 Subject: p.Peek().Range.Ptr(),
1481 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1484 close := p.recover(closeType)
1485 return &LiteralValueExpr{
1486 Val: cty.DynamicVal,
1487 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1490 p.Read() // eat 'in' keyword
1492 collExpr, collDiags := p.ParseExpression()
1493 diags = append(diags, collDiags...)
1494 if p.recovery && collDiags.HasErrors() {
1495 close := p.recover(closeType)
1496 return &LiteralValueExpr{
1497 Val: cty.DynamicVal,
1498 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1502 if p.Peek().Type != TokenColon {
1504 diags = append(diags, &hcl.Diagnostic{
1505 Severity: hcl.DiagError,
1506 Summary: "Invalid 'for' expression",
1507 Detail: "For expression requires a colon after the collection expression.",
1508 Subject: p.Peek().Range.Ptr(),
1509 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1512 close := p.recover(closeType)
1513 return &LiteralValueExpr{
1514 Val: cty.DynamicVal,
1515 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1518 p.Read() // eat colon
1520 var keyExpr, valExpr Expression
1521 var keyDiags, valDiags hcl.Diagnostics
1522 valExpr, valDiags = p.ParseExpression()
1523 if p.Peek().Type == TokenFatArrow {
1524 // What we just parsed was actually keyExpr
1525 p.Read() // eat the fat arrow
1526 keyExpr, keyDiags = valExpr, valDiags
1528 valExpr, valDiags = p.ParseExpression()
1530 diags = append(diags, keyDiags...)
1531 diags = append(diags, valDiags...)
1532 if p.recovery && (keyDiags.HasErrors() || valDiags.HasErrors()) {
1533 close := p.recover(closeType)
1534 return &LiteralValueExpr{
1535 Val: cty.DynamicVal,
1536 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1542 if p.Peek().Type == TokenEllipsis {
1547 var condExpr Expression
1548 var condDiags hcl.Diagnostics
1549 if ifKeyword.TokenMatches(p.Peek()) {
1550 p.Read() // eat "if"
1551 condExpr, condDiags = p.ParseExpression()
1552 diags = append(diags, condDiags...)
1553 if p.recovery && condDiags.HasErrors() {
1554 close := p.recover(p.oppositeBracket(open.Type))
1555 return &LiteralValueExpr{
1556 Val: cty.DynamicVal,
1557 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1563 if p.Peek().Type == closeType {
1567 diags = append(diags, &hcl.Diagnostic{
1568 Severity: hcl.DiagError,
1569 Summary: "Invalid 'for' expression",
1570 Detail: "Extra characters after the end of the 'for' expression.",
1571 Subject: p.Peek().Range.Ptr(),
1572 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1575 close = p.recover(closeType)
1580 diags = append(diags, &hcl.Diagnostic{
1581 Severity: hcl.DiagError,
1582 Summary: "Invalid 'for' expression",
1583 Detail: "Key expression is not valid when building a tuple.",
1584 Subject: keyExpr.Range().Ptr(),
1585 Context: hcl.RangeBetween(open.Range, close.Range).Ptr(),
1590 diags = append(diags, &hcl.Diagnostic{
1591 Severity: hcl.DiagError,
1592 Summary: "Invalid 'for' expression",
1593 Detail: "Grouping ellipsis (...) cannot be used when building a tuple.",
1594 Subject: &ellipsis.Range,
1595 Context: hcl.RangeBetween(open.Range, close.Range).Ptr(),
1600 diags = append(diags, &hcl.Diagnostic{
1601 Severity: hcl.DiagError,
1602 Summary: "Invalid 'for' expression",
1603 Detail: "Key expression is required when building an object.",
1604 Subject: valExpr.Range().Ptr(),
1605 Context: hcl.RangeBetween(open.Range, close.Range).Ptr(),
1619 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1620 OpenRange: open.Range,
1621 CloseRange: close.Range,
1625 // parseQuotedStringLiteral is a helper for parsing quoted strings that
1626 // aren't allowed to contain any interpolations, such as block labels.
1627 func (p *parser) parseQuotedStringLiteral() (string, hcl.Range, hcl.Diagnostics) {
1629 if oQuote.Type != TokenOQuote {
1630 return "", oQuote.Range, hcl.Diagnostics{
1632 Severity: hcl.DiagError,
1633 Summary: "Invalid string literal",
1634 Detail: "A quoted string is required here.",
1635 Subject: &oQuote.Range,
1640 var diags hcl.Diagnostics
1641 ret := &bytes.Buffer{}
1653 case TokenQuotedLit:
1654 s, sDiags := p.decodeStringLit(tok)
1655 diags = append(diags, sDiags...)
1658 case TokenTemplateControl, TokenTemplateInterp:
1660 if tok.Type == TokenTemplateControl {
1664 diags = append(diags, &hcl.Diagnostic{
1665 Severity: hcl.DiagError,
1666 Summary: "Invalid string literal",
1667 Detail: fmt.Sprintf(
1668 "Template sequences are not allowed in this string. To include a literal %q, double it (as \"%s%s\") to escape it.",
1669 which, which, which,
1671 Subject: &tok.Range,
1672 Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
1675 // Now that we're returning an error callers won't attempt to use
1676 // the result for any real operations, but they might try to use
1677 // the partial AST for other analyses, so we'll leave a marker
1678 // to indicate that there was something invalid in the string to
1679 // help avoid misinterpretation of the partial result
1680 ret.WriteString(which)
1681 ret.WriteString("{ ... }")
1683 p.recover(TokenTemplateSeqEnd) // we'll try to keep parsing after the sequence ends
1686 diags = append(diags, &hcl.Diagnostic{
1687 Severity: hcl.DiagError,
1688 Summary: "Unterminated string literal",
1689 Detail: "Unable to find the closing quote mark before the end of the file.",
1690 Subject: &tok.Range,
1691 Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
1696 // Should never happen, as long as the scanner is behaving itself
1697 diags = append(diags, &hcl.Diagnostic{
1698 Severity: hcl.DiagError,
1699 Summary: "Invalid string literal",
1700 Detail: "This item is not valid in a string literal.",
1701 Subject: &tok.Range,
1702 Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
1704 p.recover(TokenCQuote)
1711 return ret.String(), hcl.RangeBetween(oQuote.Range, cQuote.Range), diags
1714 // decodeStringLit processes the given token, which must be either a
1715 // TokenQuotedLit or a TokenStringLit, returning the string resulting from
1716 // resolving any escape sequences.
1718 // If any error diagnostics are returned, the returned string may be incomplete
1719 // or otherwise invalid.
1720 func (p *parser) decodeStringLit(tok Token) (string, hcl.Diagnostics) {
1723 case TokenQuotedLit:
1725 case TokenStringLit:
1728 panic("decodeQuotedLit can only be used with TokenStringLit and TokenQuotedLit tokens")
1730 var diags hcl.Diagnostics
1732 ret := make([]byte, 0, len(tok.Bytes))
1733 slices := scanStringLit(tok.Bytes, quoted)
1735 // We will mutate rng constantly as we walk through our token slices below.
1736 // Any diagnostics must take a copy of this rng rather than simply pointing
1737 // to it, e.g. by using rng.Ptr() rather than &rng.
1742 for _, slice := range slices {
1743 if len(slice) == 0 {
1747 // Advance the start of our range to where the previous token ended
1750 // Advance the end of our range to after our token.
1753 adv, ch, _ := textseg.ScanGraphemeClusters(b, true)
1769 // If we're not in quoted mode then just treat this token as
1770 // normal. (Slices can still start with backslash even if we're
1771 // not specifically looking for backslash sequences.)
1775 diags = append(diags, &hcl.Diagnostic{
1776 Severity: hcl.DiagError,
1777 Summary: "Invalid escape sequence",
1778 Detail: "Backslash must be followed by an escape sequence selector character.",
1787 ret = append(ret, '\n')
1790 ret = append(ret, '\r')
1793 ret = append(ret, '\t')
1796 ret = append(ret, '"')
1799 ret = append(ret, '\\')
1802 if slice[1] == 'u' && len(slice) != 6 {
1803 diags = append(diags, &hcl.Diagnostic{
1804 Severity: hcl.DiagError,
1805 Summary: "Invalid escape sequence",
1806 Detail: "The \\u escape sequence must be followed by four hexadecimal digits.",
1810 } else if slice[1] == 'U' && len(slice) != 10 {
1811 diags = append(diags, &hcl.Diagnostic{
1812 Severity: hcl.DiagError,
1813 Summary: "Invalid escape sequence",
1814 Detail: "The \\U escape sequence must be followed by eight hexadecimal digits.",
1820 numHex := string(slice[2:])
1821 num, err := strconv.ParseUint(numHex, 16, 32)
1823 // Should never happen because the scanner won't match
1824 // a sequence of digits that isn't valid.
1829 l := utf8.RuneLen(r)
1831 diags = append(diags, &hcl.Diagnostic{
1832 Severity: hcl.DiagError,
1833 Summary: "Invalid escape sequence",
1834 Detail: fmt.Sprintf("Cannot encode character U+%04x in UTF-8.", num),
1839 for i := 0; i < l; i++ {
1840 ret = append(ret, 0)
1842 rb := ret[len(ret)-l:]
1843 utf8.EncodeRune(rb, r)
1848 diags = append(diags, &hcl.Diagnostic{
1849 Severity: hcl.DiagError,
1850 Summary: "Invalid escape sequence",
1851 Detail: fmt.Sprintf("The symbol %q is not a valid escape sequence selector.", slice[1:]),
1854 ret = append(ret, slice[1:]...)
1859 if len(slice) != 3 {
1860 // Not long enough to be our escape sequence, so it's literal.
1864 if slice[1] == slice[0] && slice[2] == '{' {
1865 ret = append(ret, slice[0])
1866 ret = append(ret, '{')
1873 // If we fall out here or break out of here from the switch above
1874 // then this slice is just a literal.
1875 ret = append(ret, slice...)
1878 return string(ret), diags
1881 // setRecovery turns on recovery mode without actually doing any recovery.
1882 // This can be used when a parser knowingly leaves the peeker in a useless
1883 // place and wants to suppress errors that might result from that decision.
1884 func (p *parser) setRecovery() {
1888 // recover seeks forward in the token stream until it finds TokenType "end",
1889 // then returns with the peeker pointed at the following token.
1891 // If the given token type is a bracketer, this function will additionally
1892 // count nested instances of the brackets to try to leave the peeker at
1893 // the end of the _current_ instance of that bracketer, skipping over any
1894 // nested instances. This is a best-effort operation and may have
1895 // unpredictable results on input with bad bracketer nesting.
1896 func (p *parser) recover(end TokenType) Token {
1897 start := p.oppositeBracket(end)
1904 if end == TokenTemplateSeqEnd && ty == TokenTemplateControl {
1905 // normalize so that our matching behavior can work, since
1906 // TokenTemplateControl/TokenTemplateInterp are asymmetrical
1907 // with TokenTemplateSeqEnd and thus we need to count both
1908 // openers if that's the closer we're looking for.
1909 ty = TokenTemplateInterp
1927 // recoverOver seeks forward in the token stream until it finds a block
1928 // starting with TokenType "start", then finds the corresponding end token,
1929 // leaving the peeker pointed at the token after that end token.
1931 // The given token type _must_ be a bracketer. For example, if the given
1932 // start token is TokenOBrace then the parser will be left at the _end_ of
1933 // the next brace-delimited block encountered, or at EOF if no such block
1934 // is found or it is unclosed.
1935 func (p *parser) recoverOver(start TokenType) {
1936 end := p.oppositeBracket(start)
1938 // find the opening bracket first
1943 case start, TokenEOF:
1948 // Now use our existing recover function to locate the _end_ of the
1949 // container we've found.
1953 func (p *parser) recoverAfterBodyItem() {
1955 var open []TokenType
1971 case TokenOBrace, TokenOBrack, TokenOParen, TokenOQuote, TokenOHeredoc, TokenTemplateInterp, TokenTemplateControl:
1972 open = append(open, tok.Type)
1974 case TokenCBrace, TokenCBrack, TokenCParen, TokenCQuote, TokenCHeredoc:
1975 opener := p.oppositeBracket(tok.Type)
1976 for len(open) > 0 && open[len(open)-1] != opener {
1977 open = open[:len(open)-1]
1980 open = open[:len(open)-1]
1983 case TokenTemplateSeqEnd:
1984 for len(open) > 0 && open[len(open)-1] != TokenTemplateInterp && open[len(open)-1] != TokenTemplateControl {
1985 open = open[:len(open)-1]
1988 open = open[:len(open)-1]
1995 // oppositeBracket finds the bracket that opposes the given bracketer, or
1996 // NilToken if the given token isn't a bracketer.
1998 // "Bracketer", for the sake of this function, is one end of a matching
1999 // open/close set of tokens that establish a bracketing context.
2000 func (p *parser) oppositeBracket(ty TokenType) TokenType {
2012 return TokenCHeredoc
2023 return TokenOHeredoc
2025 case TokenTemplateControl:
2026 return TokenTemplateSeqEnd
2027 case TokenTemplateInterp:
2028 return TokenTemplateSeqEnd
2029 case TokenTemplateSeqEnd:
2030 // This is ambigous, but we return Interp here because that's
2031 // what's assumed by the "recover" method.
2032 return TokenTemplateInterp
2039 func errPlaceholderExpr(rng hcl.Range) Expression {
2040 return &LiteralValueExpr{
2041 Val: cty.DynamicVal,