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)
857 rng := hcl.RangeBetween(open.Range, close.Range)
863 OpenRange: open.Range,
876 // makeRelativeTraversal takes an expression and a traverser and returns
877 // a traversal expression that combines the two. If the given expression
878 // is already a traversal, it is extended in place (mutating it) and
879 // returned. If it isn't, a new RelativeTraversalExpr is created and returned.
880 func makeRelativeTraversal(expr Expression, next hcl.Traverser, rng hcl.Range) Expression {
881 switch texpr := expr.(type) {
882 case *ScopeTraversalExpr:
883 texpr.Traversal = append(texpr.Traversal, next)
884 texpr.SrcRange = hcl.RangeBetween(texpr.SrcRange, rng)
886 case *RelativeTraversalExpr:
887 texpr.Traversal = append(texpr.Traversal, next)
888 texpr.SrcRange = hcl.RangeBetween(texpr.SrcRange, rng)
891 return &RelativeTraversalExpr{
893 Traversal: hcl.Traversal{next},
899 func (p *parser) parseExpressionTerm() (Expression, hcl.Diagnostics) {
904 p.Read() // eat open paren
906 p.PushIncludeNewlines(false)
908 expr, diags := p.ParseExpression()
909 if diags.HasErrors() {
910 // attempt to place the peeker after our closing paren
911 // before we return, so that the next parser has some
912 // chance of finding a valid expression.
913 p.recover(TokenCParen)
914 p.PopIncludeNewlines()
919 if close.Type != TokenCParen {
920 diags = append(diags, &hcl.Diagnostic{
921 Severity: hcl.DiagError,
922 Summary: "Unbalanced parentheses",
923 Detail: "Expected a closing parenthesis to terminate the expression.",
924 Subject: &close.Range,
925 Context: hcl.RangeBetween(start.Range, close.Range).Ptr(),
930 p.Read() // eat closing paren
931 p.PopIncludeNewlines()
936 tok := p.Read() // eat number token
938 numVal, diags := p.numberLitValue(tok)
939 return &LiteralValueExpr{
945 tok := p.Read() // eat identifier token
947 if p.Peek().Type == TokenOParen {
948 return p.finishParsingFunctionCall(tok)
951 name := string(tok.Bytes)
954 return &LiteralValueExpr{
959 return &LiteralValueExpr{
964 return &LiteralValueExpr{
965 Val: cty.NullVal(cty.DynamicPseudoType),
969 return &ScopeTraversalExpr{
970 Traversal: hcl.Traversal{
980 case TokenOQuote, TokenOHeredoc:
981 open := p.Read() // eat opening marker
982 closer := p.oppositeBracket(open.Type)
983 exprs, passthru, _, diags := p.parseTemplateInner(closer, tokenOpensFlushHeredoc(open))
985 closeRange := p.PrevRange()
989 panic("passthru set with len(exprs) != 1")
991 return &TemplateWrapExpr{
993 SrcRange: hcl.RangeBetween(open.Range, closeRange),
997 return &TemplateExpr{
999 SrcRange: hcl.RangeBetween(open.Range, closeRange),
1003 tok := p.Read() // eat minus token
1005 // Important to use parseExpressionWithTraversals rather than parseExpression
1006 // here, otherwise we can capture a following binary expression into
1008 // e.g. -46+5 should parse as (-46)+5, not -(46+5)
1009 operand, diags := p.parseExpressionWithTraversals()
1010 return &UnaryOpExpr{
1014 SrcRange: hcl.RangeBetween(tok.Range, operand.Range()),
1015 SymbolRange: tok.Range,
1019 tok := p.Read() // eat bang token
1021 // Important to use parseExpressionWithTraversals rather than parseExpression
1022 // here, otherwise we can capture a following binary expression into
1024 operand, diags := p.parseExpressionWithTraversals()
1025 return &UnaryOpExpr{
1029 SrcRange: hcl.RangeBetween(tok.Range, operand.Range()),
1030 SymbolRange: tok.Range,
1034 return p.parseTupleCons()
1037 return p.parseObjectCons()
1040 var diags hcl.Diagnostics
1042 diags = append(diags, &hcl.Diagnostic{
1043 Severity: hcl.DiagError,
1044 Summary: "Invalid expression",
1045 Detail: "Expected the start of an expression, but found an invalid expression token.",
1046 Subject: &start.Range,
1051 // Return a placeholder so that the AST is still structurally sound
1052 // even in the presence of parse errors.
1053 return &LiteralValueExpr{
1054 Val: cty.DynamicVal,
1055 SrcRange: start.Range,
1060 func (p *parser) numberLitValue(tok Token) (cty.Value, hcl.Diagnostics) {
1061 // The cty.ParseNumberVal is always the same behavior as converting a
1062 // string to a number, ensuring we always interpret decimal numbers in
1064 numVal, err := cty.ParseNumberVal(string(tok.Bytes))
1066 ret := cty.UnknownVal(cty.Number)
1067 return ret, hcl.Diagnostics{
1069 Severity: hcl.DiagError,
1070 Summary: "Invalid number literal",
1071 // FIXME: not a very good error message, but convert only
1072 // gives us "a number is required", so not much help either.
1073 Detail: "Failed to recognize the value of this number literal.",
1074 Subject: &tok.Range,
1081 // finishParsingFunctionCall parses a function call assuming that the function
1082 // name was already read, and so the peeker should be pointing at the opening
1083 // parenthesis after the name.
1084 func (p *parser) finishParsingFunctionCall(name Token) (Expression, hcl.Diagnostics) {
1086 if openTok.Type != TokenOParen {
1087 // should never happen if callers behave
1088 panic("finishParsingFunctionCall called with non-parenthesis as next token")
1091 var args []Expression
1092 var diags hcl.Diagnostics
1093 var expandFinal bool
1096 // Arbitrary newlines are allowed inside the function call parentheses.
1097 p.PushIncludeNewlines(false)
1103 if tok.Type == TokenCParen {
1104 closeTok = p.Read() // eat closing paren
1108 arg, argDiags := p.ParseExpression()
1109 args = append(args, arg)
1110 diags = append(diags, argDiags...)
1111 if p.recovery && argDiags.HasErrors() {
1112 // if there was a parse error in the argument then we've
1113 // probably been left in a weird place in the token stream,
1114 // so we'll bail out with a partial argument list.
1115 p.recover(TokenCParen)
1120 if sep.Type == TokenCParen {
1125 if sep.Type == TokenEllipsis {
1128 if p.Peek().Type != TokenCParen {
1130 diags = append(diags, &hcl.Diagnostic{
1131 Severity: hcl.DiagError,
1132 Summary: "Missing closing parenthesis",
1133 Detail: "An expanded function argument (with ...) must be immediately followed by closing parentheses.",
1134 Subject: &sep.Range,
1135 Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(),
1138 closeTok = p.recover(TokenCParen)
1140 closeTok = p.Read() // eat closing paren
1145 if sep.Type != TokenComma {
1146 diags = append(diags, &hcl.Diagnostic{
1147 Severity: hcl.DiagError,
1148 Summary: "Missing argument separator",
1149 Detail: "A comma is required to separate each function argument from the next.",
1150 Subject: &sep.Range,
1151 Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(),
1153 closeTok = p.recover(TokenCParen)
1157 if p.Peek().Type == TokenCParen {
1158 // A trailing comma after the last argument gets us in here.
1159 closeTok = p.Read() // eat closing paren
1165 p.PopIncludeNewlines()
1167 return &FunctionCallExpr{
1168 Name: string(name.Bytes),
1171 ExpandFinal: expandFinal,
1173 NameRange: name.Range,
1174 OpenParenRange: openTok.Range,
1175 CloseParenRange: closeTok.Range,
1179 func (p *parser) parseTupleCons() (Expression, hcl.Diagnostics) {
1181 if open.Type != TokenOBrack {
1182 // Should never happen if callers are behaving
1183 panic("parseTupleCons called without peeker pointing to open bracket")
1186 p.PushIncludeNewlines(false)
1187 defer p.PopIncludeNewlines()
1189 if forKeyword.TokenMatches(p.Peek()) {
1190 return p.finishParsingForExpr(open)
1195 var diags hcl.Diagnostics
1196 var exprs []Expression
1200 if next.Type == TokenCBrack {
1201 close = p.Read() // eat closer
1205 expr, exprDiags := p.ParseExpression()
1206 exprs = append(exprs, expr)
1207 diags = append(diags, exprDiags...)
1209 if p.recovery && exprDiags.HasErrors() {
1210 // If expression parsing failed then we are probably in a strange
1211 // place in the token stream, so we'll bail out and try to reset
1212 // to after our closing bracket to allow parsing to continue.
1213 close = p.recover(TokenCBrack)
1218 if next.Type == TokenCBrack {
1219 close = p.Read() // eat closer
1223 if next.Type != TokenComma {
1225 diags = append(diags, &hcl.Diagnostic{
1226 Severity: hcl.DiagError,
1227 Summary: "Missing item separator",
1228 Detail: "Expected a comma to mark the beginning of the next item.",
1229 Subject: &next.Range,
1230 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
1233 close = p.recover(TokenCBrack)
1237 p.Read() // eat comma
1241 return &TupleConsExpr{
1244 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1245 OpenRange: open.Range,
1249 func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) {
1251 if open.Type != TokenOBrace {
1252 // Should never happen if callers are behaving
1253 panic("parseObjectCons called without peeker pointing to open brace")
1256 // We must temporarily stop looking at newlines here while we check for
1257 // a "for" keyword, since for expressions are _not_ newline-sensitive,
1258 // even though object constructors are.
1259 p.PushIncludeNewlines(false)
1260 isFor := forKeyword.TokenMatches(p.Peek())
1261 p.PopIncludeNewlines()
1263 return p.finishParsingForExpr(open)
1266 p.PushIncludeNewlines(true)
1267 defer p.PopIncludeNewlines()
1271 var diags hcl.Diagnostics
1272 var items []ObjectConsItem
1276 if next.Type == TokenNewline {
1277 p.Read() // eat newline
1281 if next.Type == TokenCBrace {
1282 close = p.Read() // eat closer
1287 var keyDiags hcl.Diagnostics
1288 key, keyDiags = p.ParseExpression()
1289 diags = append(diags, keyDiags...)
1291 if p.recovery && keyDiags.HasErrors() {
1292 // If expression parsing failed then we are probably in a strange
1293 // place in the token stream, so we'll bail out and try to reset
1294 // to after our closing brace to allow parsing to continue.
1295 close = p.recover(TokenCBrace)
1299 // We wrap up the key expression in a special wrapper that deals
1300 // with our special case that naked identifiers as object keys
1301 // are interpreted as literal strings.
1302 key = &ObjectConsKeyExpr{Wrapped: key}
1305 if next.Type != TokenEqual && next.Type != TokenColon {
1308 case TokenNewline, TokenComma:
1309 diags = append(diags, &hcl.Diagnostic{
1310 Severity: hcl.DiagError,
1311 Summary: "Missing attribute value",
1312 Detail: "Expected an attribute value, introduced by an equals sign (\"=\").",
1313 Subject: &next.Range,
1314 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
1317 // Although this might just be a plain old missing equals
1318 // sign before a reference, one way to get here is to try
1319 // to write an attribute name containing a period followed
1320 // by a digit, which was valid in HCL1, like this:
1321 // foo1.2_bar = "baz"
1322 // We can't know exactly what the user intended here, but
1323 // we'll augment our message with an extra hint in this case
1324 // in case it is helpful.
1325 diags = append(diags, &hcl.Diagnostic{
1326 Severity: hcl.DiagError,
1327 Summary: "Missing key/value separator",
1328 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.",
1329 Subject: &next.Range,
1330 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
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.",
1337 Subject: &next.Range,
1338 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
1342 close = p.recover(TokenCBrace)
1346 p.Read() // eat equals sign or colon
1348 value, valueDiags := p.ParseExpression()
1349 diags = append(diags, valueDiags...)
1351 if p.recovery && valueDiags.HasErrors() {
1352 // If expression parsing failed then we are probably in a strange
1353 // place in the token stream, so we'll bail out and try to reset
1354 // to after our closing brace to allow parsing to continue.
1355 close = p.recover(TokenCBrace)
1359 items = append(items, ObjectConsItem{
1365 if next.Type == TokenCBrace {
1366 close = p.Read() // eat closer
1370 if next.Type != TokenComma && next.Type != TokenNewline {
1372 diags = append(diags, &hcl.Diagnostic{
1373 Severity: hcl.DiagError,
1374 Summary: "Missing attribute separator",
1375 Detail: "Expected a newline or comma to mark the beginning of the next attribute.",
1376 Subject: &next.Range,
1377 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(),
1380 close = p.recover(TokenCBrace)
1384 p.Read() // eat comma or newline
1388 return &ObjectConsExpr{
1391 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1392 OpenRange: open.Range,
1396 func (p *parser) finishParsingForExpr(open Token) (Expression, hcl.Diagnostics) {
1397 p.PushIncludeNewlines(false)
1398 defer p.PopIncludeNewlines()
1399 introducer := p.Read()
1400 if !forKeyword.TokenMatches(introducer) {
1401 // Should never happen if callers are behaving
1402 panic("finishParsingForExpr called without peeker pointing to 'for' identifier")
1406 var closeType TokenType
1410 closeType = TokenCBrace
1412 makeObj = false // making a tuple
1413 closeType = TokenCBrack
1415 // Should never happen if callers are behaving
1416 panic("finishParsingForExpr called with invalid open token")
1419 var diags hcl.Diagnostics
1420 var keyName, valName string
1422 if p.Peek().Type != TokenIdent {
1424 diags = append(diags, &hcl.Diagnostic{
1425 Severity: hcl.DiagError,
1426 Summary: "Invalid 'for' expression",
1427 Detail: "For expression requires variable name after 'for'.",
1428 Subject: p.Peek().Range.Ptr(),
1429 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1432 close := p.recover(closeType)
1433 return &LiteralValueExpr{
1434 Val: cty.DynamicVal,
1435 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1439 valName = string(p.Read().Bytes)
1441 if p.Peek().Type == TokenComma {
1442 // What we just read was actually the key, then.
1444 p.Read() // eat comma
1446 if p.Peek().Type != TokenIdent {
1448 diags = append(diags, &hcl.Diagnostic{
1449 Severity: hcl.DiagError,
1450 Summary: "Invalid 'for' expression",
1451 Detail: "For expression requires value variable name after comma.",
1452 Subject: p.Peek().Range.Ptr(),
1453 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1456 close := p.recover(closeType)
1457 return &LiteralValueExpr{
1458 Val: cty.DynamicVal,
1459 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1463 valName = string(p.Read().Bytes)
1466 if !inKeyword.TokenMatches(p.Peek()) {
1468 diags = append(diags, &hcl.Diagnostic{
1469 Severity: hcl.DiagError,
1470 Summary: "Invalid 'for' expression",
1471 Detail: "For expression requires the 'in' keyword after its name declarations.",
1472 Subject: p.Peek().Range.Ptr(),
1473 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1476 close := p.recover(closeType)
1477 return &LiteralValueExpr{
1478 Val: cty.DynamicVal,
1479 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1482 p.Read() // eat 'in' keyword
1484 collExpr, collDiags := p.ParseExpression()
1485 diags = append(diags, collDiags...)
1486 if p.recovery && collDiags.HasErrors() {
1487 close := p.recover(closeType)
1488 return &LiteralValueExpr{
1489 Val: cty.DynamicVal,
1490 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1494 if p.Peek().Type != TokenColon {
1496 diags = append(diags, &hcl.Diagnostic{
1497 Severity: hcl.DiagError,
1498 Summary: "Invalid 'for' expression",
1499 Detail: "For expression requires a colon after the collection expression.",
1500 Subject: p.Peek().Range.Ptr(),
1501 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1504 close := p.recover(closeType)
1505 return &LiteralValueExpr{
1506 Val: cty.DynamicVal,
1507 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1510 p.Read() // eat colon
1512 var keyExpr, valExpr Expression
1513 var keyDiags, valDiags hcl.Diagnostics
1514 valExpr, valDiags = p.ParseExpression()
1515 if p.Peek().Type == TokenFatArrow {
1516 // What we just parsed was actually keyExpr
1517 p.Read() // eat the fat arrow
1518 keyExpr, keyDiags = valExpr, valDiags
1520 valExpr, valDiags = p.ParseExpression()
1522 diags = append(diags, keyDiags...)
1523 diags = append(diags, valDiags...)
1524 if p.recovery && (keyDiags.HasErrors() || valDiags.HasErrors()) {
1525 close := p.recover(closeType)
1526 return &LiteralValueExpr{
1527 Val: cty.DynamicVal,
1528 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1534 if p.Peek().Type == TokenEllipsis {
1539 var condExpr Expression
1540 var condDiags hcl.Diagnostics
1541 if ifKeyword.TokenMatches(p.Peek()) {
1542 p.Read() // eat "if"
1543 condExpr, condDiags = p.ParseExpression()
1544 diags = append(diags, condDiags...)
1545 if p.recovery && condDiags.HasErrors() {
1546 close := p.recover(p.oppositeBracket(open.Type))
1547 return &LiteralValueExpr{
1548 Val: cty.DynamicVal,
1549 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1555 if p.Peek().Type == closeType {
1559 diags = append(diags, &hcl.Diagnostic{
1560 Severity: hcl.DiagError,
1561 Summary: "Invalid 'for' expression",
1562 Detail: "Extra characters after the end of the 'for' expression.",
1563 Subject: p.Peek().Range.Ptr(),
1564 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(),
1567 close = p.recover(closeType)
1572 diags = append(diags, &hcl.Diagnostic{
1573 Severity: hcl.DiagError,
1574 Summary: "Invalid 'for' expression",
1575 Detail: "Key expression is not valid when building a tuple.",
1576 Subject: keyExpr.Range().Ptr(),
1577 Context: hcl.RangeBetween(open.Range, close.Range).Ptr(),
1582 diags = append(diags, &hcl.Diagnostic{
1583 Severity: hcl.DiagError,
1584 Summary: "Invalid 'for' expression",
1585 Detail: "Grouping ellipsis (...) cannot be used when building a tuple.",
1586 Subject: &ellipsis.Range,
1587 Context: hcl.RangeBetween(open.Range, close.Range).Ptr(),
1592 diags = append(diags, &hcl.Diagnostic{
1593 Severity: hcl.DiagError,
1594 Summary: "Invalid 'for' expression",
1595 Detail: "Key expression is required when building an object.",
1596 Subject: valExpr.Range().Ptr(),
1597 Context: hcl.RangeBetween(open.Range, close.Range).Ptr(),
1611 SrcRange: hcl.RangeBetween(open.Range, close.Range),
1612 OpenRange: open.Range,
1613 CloseRange: close.Range,
1617 // parseQuotedStringLiteral is a helper for parsing quoted strings that
1618 // aren't allowed to contain any interpolations, such as block labels.
1619 func (p *parser) parseQuotedStringLiteral() (string, hcl.Range, hcl.Diagnostics) {
1621 if oQuote.Type != TokenOQuote {
1622 return "", oQuote.Range, hcl.Diagnostics{
1624 Severity: hcl.DiagError,
1625 Summary: "Invalid string literal",
1626 Detail: "A quoted string is required here.",
1627 Subject: &oQuote.Range,
1632 var diags hcl.Diagnostics
1633 ret := &bytes.Buffer{}
1645 case TokenQuotedLit:
1646 s, sDiags := p.decodeStringLit(tok)
1647 diags = append(diags, sDiags...)
1650 case TokenTemplateControl, TokenTemplateInterp:
1652 if tok.Type == TokenTemplateControl {
1656 diags = append(diags, &hcl.Diagnostic{
1657 Severity: hcl.DiagError,
1658 Summary: "Invalid string literal",
1659 Detail: fmt.Sprintf(
1660 "Template sequences are not allowed in this string. To include a literal %q, double it (as \"%s%s\") to escape it.",
1661 which, which, which,
1663 Subject: &tok.Range,
1664 Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
1667 // Now that we're returning an error callers won't attempt to use
1668 // the result for any real operations, but they might try to use
1669 // the partial AST for other analyses, so we'll leave a marker
1670 // to indicate that there was something invalid in the string to
1671 // help avoid misinterpretation of the partial result
1672 ret.WriteString(which)
1673 ret.WriteString("{ ... }")
1675 p.recover(TokenTemplateSeqEnd) // we'll try to keep parsing after the sequence ends
1678 diags = append(diags, &hcl.Diagnostic{
1679 Severity: hcl.DiagError,
1680 Summary: "Unterminated string literal",
1681 Detail: "Unable to find the closing quote mark before the end of the file.",
1682 Subject: &tok.Range,
1683 Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
1688 // Should never happen, as long as the scanner is behaving itself
1689 diags = append(diags, &hcl.Diagnostic{
1690 Severity: hcl.DiagError,
1691 Summary: "Invalid string literal",
1692 Detail: "This item is not valid in a string literal.",
1693 Subject: &tok.Range,
1694 Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
1696 p.recover(TokenCQuote)
1703 return ret.String(), hcl.RangeBetween(oQuote.Range, cQuote.Range), diags
1706 // decodeStringLit processes the given token, which must be either a
1707 // TokenQuotedLit or a TokenStringLit, returning the string resulting from
1708 // resolving any escape sequences.
1710 // If any error diagnostics are returned, the returned string may be incomplete
1711 // or otherwise invalid.
1712 func (p *parser) decodeStringLit(tok Token) (string, hcl.Diagnostics) {
1715 case TokenQuotedLit:
1717 case TokenStringLit:
1720 panic("decodeQuotedLit can only be used with TokenStringLit and TokenQuotedLit tokens")
1722 var diags hcl.Diagnostics
1724 ret := make([]byte, 0, len(tok.Bytes))
1725 slices := scanStringLit(tok.Bytes, quoted)
1727 // We will mutate rng constantly as we walk through our token slices below.
1728 // Any diagnostics must take a copy of this rng rather than simply pointing
1729 // to it, e.g. by using rng.Ptr() rather than &rng.
1734 for _, slice := range slices {
1735 if len(slice) == 0 {
1739 // Advance the start of our range to where the previous token ended
1742 // Advance the end of our range to after our token.
1745 adv, ch, _ := textseg.ScanGraphemeClusters(b, true)
1761 // If we're not in quoted mode then just treat this token as
1762 // normal. (Slices can still start with backslash even if we're
1763 // not specifically looking for backslash sequences.)
1767 diags = append(diags, &hcl.Diagnostic{
1768 Severity: hcl.DiagError,
1769 Summary: "Invalid escape sequence",
1770 Detail: "Backslash must be followed by an escape sequence selector character.",
1779 ret = append(ret, '\n')
1782 ret = append(ret, '\r')
1785 ret = append(ret, '\t')
1788 ret = append(ret, '"')
1791 ret = append(ret, '\\')
1794 if slice[1] == 'u' && len(slice) != 6 {
1795 diags = append(diags, &hcl.Diagnostic{
1796 Severity: hcl.DiagError,
1797 Summary: "Invalid escape sequence",
1798 Detail: "The \\u escape sequence must be followed by four hexadecimal digits.",
1802 } else if slice[1] == 'U' && len(slice) != 10 {
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 eight hexadecimal digits.",
1812 numHex := string(slice[2:])
1813 num, err := strconv.ParseUint(numHex, 16, 32)
1815 // Should never happen because the scanner won't match
1816 // a sequence of digits that isn't valid.
1821 l := utf8.RuneLen(r)
1823 diags = append(diags, &hcl.Diagnostic{
1824 Severity: hcl.DiagError,
1825 Summary: "Invalid escape sequence",
1826 Detail: fmt.Sprintf("Cannot encode character U+%04x in UTF-8.", num),
1831 for i := 0; i < l; i++ {
1832 ret = append(ret, 0)
1834 rb := ret[len(ret)-l:]
1835 utf8.EncodeRune(rb, r)
1840 diags = append(diags, &hcl.Diagnostic{
1841 Severity: hcl.DiagError,
1842 Summary: "Invalid escape sequence",
1843 Detail: fmt.Sprintf("The symbol %q is not a valid escape sequence selector.", slice[1:]),
1846 ret = append(ret, slice[1:]...)
1851 if len(slice) != 3 {
1852 // Not long enough to be our escape sequence, so it's literal.
1856 if slice[1] == slice[0] && slice[2] == '{' {
1857 ret = append(ret, slice[0])
1858 ret = append(ret, '{')
1865 // If we fall out here or break out of here from the switch above
1866 // then this slice is just a literal.
1867 ret = append(ret, slice...)
1870 return string(ret), diags
1873 // setRecovery turns on recovery mode without actually doing any recovery.
1874 // This can be used when a parser knowingly leaves the peeker in a useless
1875 // place and wants to suppress errors that might result from that decision.
1876 func (p *parser) setRecovery() {
1880 // recover seeks forward in the token stream until it finds TokenType "end",
1881 // then returns with the peeker pointed at the following token.
1883 // If the given token type is a bracketer, this function will additionally
1884 // count nested instances of the brackets to try to leave the peeker at
1885 // the end of the _current_ instance of that bracketer, skipping over any
1886 // nested instances. This is a best-effort operation and may have
1887 // unpredictable results on input with bad bracketer nesting.
1888 func (p *parser) recover(end TokenType) Token {
1889 start := p.oppositeBracket(end)
1896 if end == TokenTemplateSeqEnd && ty == TokenTemplateControl {
1897 // normalize so that our matching behavior can work, since
1898 // TokenTemplateControl/TokenTemplateInterp are asymmetrical
1899 // with TokenTemplateSeqEnd and thus we need to count both
1900 // openers if that's the closer we're looking for.
1901 ty = TokenTemplateInterp
1919 // recoverOver seeks forward in the token stream until it finds a block
1920 // starting with TokenType "start", then finds the corresponding end token,
1921 // leaving the peeker pointed at the token after that end token.
1923 // The given token type _must_ be a bracketer. For example, if the given
1924 // start token is TokenOBrace then the parser will be left at the _end_ of
1925 // the next brace-delimited block encountered, or at EOF if no such block
1926 // is found or it is unclosed.
1927 func (p *parser) recoverOver(start TokenType) {
1928 end := p.oppositeBracket(start)
1930 // find the opening bracket first
1935 case start, TokenEOF:
1940 // Now use our existing recover function to locate the _end_ of the
1941 // container we've found.
1945 func (p *parser) recoverAfterBodyItem() {
1947 var open []TokenType
1963 case TokenOBrace, TokenOBrack, TokenOParen, TokenOQuote, TokenOHeredoc, TokenTemplateInterp, TokenTemplateControl:
1964 open = append(open, tok.Type)
1966 case TokenCBrace, TokenCBrack, TokenCParen, TokenCQuote, TokenCHeredoc:
1967 opener := p.oppositeBracket(tok.Type)
1968 for len(open) > 0 && open[len(open)-1] != opener {
1969 open = open[:len(open)-1]
1972 open = open[:len(open)-1]
1975 case TokenTemplateSeqEnd:
1976 for len(open) > 0 && open[len(open)-1] != TokenTemplateInterp && open[len(open)-1] != TokenTemplateControl {
1977 open = open[:len(open)-1]
1980 open = open[:len(open)-1]
1987 // oppositeBracket finds the bracket that opposes the given bracketer, or
1988 // NilToken if the given token isn't a bracketer.
1990 // "Bracketer", for the sake of this function, is one end of a matching
1991 // open/close set of tokens that establish a bracketing context.
1992 func (p *parser) oppositeBracket(ty TokenType) TokenType {
2004 return TokenCHeredoc
2015 return TokenOHeredoc
2017 case TokenTemplateControl:
2018 return TokenTemplateSeqEnd
2019 case TokenTemplateInterp:
2020 return TokenTemplateSeqEnd
2021 case TokenTemplateSeqEnd:
2022 // This is ambigous, but we return Interp here because that's
2023 // what's assumed by the "recover" method.
2024 return TokenTemplateInterp
2031 func errPlaceholderExpr(rng hcl.Range) Expression {
2032 return &LiteralValueExpr{
2033 Val: cty.DynamicVal,