github.com/hashicorp/hcl/v2@v2.20.0/hclsyntax/parser.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclsyntax 5 6 import ( 7 "bytes" 8 "fmt" 9 "strconv" 10 "unicode/utf8" 11 12 "github.com/apparentlymart/go-textseg/v15/textseg" 13 "github.com/hashicorp/hcl/v2" 14 "github.com/zclconf/go-cty/cty" 15 ) 16 17 type parser struct { 18 *peeker 19 20 // set to true if any recovery is attempted. The parser can use this 21 // to attempt to reduce error noise by suppressing "bad token" errors 22 // in recovery mode, assuming that the recovery heuristics have failed 23 // in this case and left the peeker in a wrong place. 24 recovery bool 25 } 26 27 func (p *parser) ParseBody(end TokenType) (*Body, hcl.Diagnostics) { 28 attrs := Attributes{} 29 blocks := Blocks{} 30 var diags hcl.Diagnostics 31 32 startRange := p.PrevRange() 33 var endRange hcl.Range 34 35 Token: 36 for { 37 next := p.Peek() 38 if next.Type == end { 39 endRange = p.NextRange() 40 p.Read() 41 break Token 42 } 43 44 switch next.Type { 45 case TokenNewline: 46 p.Read() 47 continue 48 case TokenIdent: 49 item, itemDiags := p.ParseBodyItem() 50 diags = append(diags, itemDiags...) 51 switch titem := item.(type) { 52 case *Block: 53 blocks = append(blocks, titem) 54 case *Attribute: 55 if existing, exists := attrs[titem.Name]; exists { 56 diags = append(diags, &hcl.Diagnostic{ 57 Severity: hcl.DiagError, 58 Summary: "Attribute redefined", 59 Detail: fmt.Sprintf( 60 "The argument %q was already set at %s. Each argument may be set only once.", 61 titem.Name, existing.NameRange.String(), 62 ), 63 Subject: &titem.NameRange, 64 }) 65 } else { 66 attrs[titem.Name] = titem 67 } 68 default: 69 // This should never happen for valid input, but may if a 70 // syntax error was detected in ParseBodyItem that prevented 71 // it from even producing a partially-broken item. In that 72 // case, it would've left at least one error in the diagnostics 73 // slice we already dealt with above. 74 // 75 // We'll assume ParseBodyItem attempted recovery to leave 76 // us in a reasonable position to try parsing the next item. 77 continue 78 } 79 default: 80 bad := p.Read() 81 if !p.recovery { 82 switch bad.Type { 83 case TokenOQuote: 84 diags = append(diags, &hcl.Diagnostic{ 85 Severity: hcl.DiagError, 86 Summary: "Invalid argument name", 87 Detail: "Argument names must not be quoted.", 88 Subject: &bad.Range, 89 }) 90 case TokenEOF: 91 switch end { 92 case TokenCBrace: 93 // If we're looking for a closing brace then we're parsing a block 94 diags = append(diags, &hcl.Diagnostic{ 95 Severity: hcl.DiagError, 96 Summary: "Unclosed configuration block", 97 Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", 98 Subject: &startRange, 99 }) 100 default: 101 // The only other "end" should itself be TokenEOF (for 102 // the top-level body) and so we shouldn't get here, 103 // but we'll return a generic error message anyway to 104 // be resilient. 105 diags = append(diags, &hcl.Diagnostic{ 106 Severity: hcl.DiagError, 107 Summary: "Unclosed configuration body", 108 Detail: "Found end of file before the end of this configuration body.", 109 Subject: &startRange, 110 }) 111 } 112 default: 113 diags = append(diags, &hcl.Diagnostic{ 114 Severity: hcl.DiagError, 115 Summary: "Argument or block definition required", 116 Detail: "An argument or block definition is required here.", 117 Subject: &bad.Range, 118 }) 119 } 120 } 121 endRange = p.PrevRange() // arbitrary, but somewhere inside the body means better diagnostics 122 123 p.recover(end) // attempt to recover to the token after the end of this body 124 break Token 125 } 126 } 127 128 return &Body{ 129 Attributes: attrs, 130 Blocks: blocks, 131 132 SrcRange: hcl.RangeBetween(startRange, endRange), 133 EndRange: hcl.Range{ 134 Filename: endRange.Filename, 135 Start: endRange.End, 136 End: endRange.End, 137 }, 138 }, diags 139 } 140 141 func (p *parser) ParseBodyItem() (Node, hcl.Diagnostics) { 142 ident := p.Read() 143 if ident.Type != TokenIdent { 144 p.recoverAfterBodyItem() 145 return nil, hcl.Diagnostics{ 146 { 147 Severity: hcl.DiagError, 148 Summary: "Argument or block definition required", 149 Detail: "An argument or block definition is required here.", 150 Subject: &ident.Range, 151 }, 152 } 153 } 154 155 next := p.Peek() 156 157 switch next.Type { 158 case TokenEqual: 159 return p.finishParsingBodyAttribute(ident, false) 160 case TokenOQuote, TokenOBrace, TokenIdent: 161 return p.finishParsingBodyBlock(ident) 162 default: 163 p.recoverAfterBodyItem() 164 return nil, hcl.Diagnostics{ 165 { 166 Severity: hcl.DiagError, 167 Summary: "Argument or block definition required", 168 Detail: "An argument or block definition is required here. To set an argument, use the equals sign \"=\" to introduce the argument value.", 169 Subject: &ident.Range, 170 }, 171 } 172 } 173 } 174 175 // parseSingleAttrBody is a weird variant of ParseBody that deals with the 176 // body of a nested block containing only one attribute value all on a single 177 // line, like foo { bar = baz } . It expects to find a single attribute item 178 // immediately followed by the end token type with no intervening newlines. 179 func (p *parser) parseSingleAttrBody(end TokenType) (*Body, hcl.Diagnostics) { 180 ident := p.Read() 181 if ident.Type != TokenIdent { 182 p.recoverAfterBodyItem() 183 return nil, hcl.Diagnostics{ 184 { 185 Severity: hcl.DiagError, 186 Summary: "Argument or block definition required", 187 Detail: "An argument or block definition is required here.", 188 Subject: &ident.Range, 189 }, 190 } 191 } 192 193 var attr *Attribute 194 var diags hcl.Diagnostics 195 196 next := p.Peek() 197 198 switch next.Type { 199 case TokenEqual: 200 node, attrDiags := p.finishParsingBodyAttribute(ident, true) 201 diags = append(diags, attrDiags...) 202 attr = node.(*Attribute) 203 case TokenOQuote, TokenOBrace, TokenIdent: 204 p.recoverAfterBodyItem() 205 return nil, hcl.Diagnostics{ 206 { 207 Severity: hcl.DiagError, 208 Summary: "Argument definition required", 209 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), 210 Subject: hcl.RangeBetween(ident.Range, next.Range).Ptr(), 211 }, 212 } 213 default: 214 p.recoverAfterBodyItem() 215 return nil, hcl.Diagnostics{ 216 { 217 Severity: hcl.DiagError, 218 Summary: "Argument or block definition required", 219 Detail: "An argument or block definition is required here. To set an argument, use the equals sign \"=\" to introduce the argument value.", 220 Subject: &ident.Range, 221 }, 222 } 223 } 224 225 return &Body{ 226 Attributes: Attributes{ 227 string(ident.Bytes): attr, 228 }, 229 230 SrcRange: attr.SrcRange, 231 EndRange: hcl.Range{ 232 Filename: attr.SrcRange.Filename, 233 Start: attr.SrcRange.End, 234 End: attr.SrcRange.End, 235 }, 236 }, diags 237 238 } 239 240 func (p *parser) finishParsingBodyAttribute(ident Token, singleLine bool) (Node, hcl.Diagnostics) { 241 eqTok := p.Read() // eat equals token 242 if eqTok.Type != TokenEqual { 243 // should never happen if caller behaves 244 panic("finishParsingBodyAttribute called with next not equals") 245 } 246 247 var endRange hcl.Range 248 249 expr, diags := p.ParseExpression() 250 if p.recovery && diags.HasErrors() { 251 // recovery within expressions tends to be tricky, so we've probably 252 // landed somewhere weird. We'll try to reset to the start of a body 253 // item so parsing can continue. 254 endRange = p.PrevRange() 255 p.recoverAfterBodyItem() 256 } else { 257 endRange = p.PrevRange() 258 if !singleLine { 259 end := p.Peek() 260 if end.Type != TokenNewline && end.Type != TokenEOF { 261 if !p.recovery { 262 summary := "Missing newline after argument" 263 detail := "An argument definition must end with a newline." 264 265 if end.Type == TokenComma { 266 summary = "Unexpected comma after argument" 267 detail = "Argument definitions must be separated by newlines, not commas. " + detail 268 } 269 270 diags = append(diags, &hcl.Diagnostic{ 271 Severity: hcl.DiagError, 272 Summary: summary, 273 Detail: detail, 274 Subject: &end.Range, 275 Context: hcl.RangeBetween(ident.Range, end.Range).Ptr(), 276 }) 277 } 278 endRange = p.PrevRange() 279 p.recoverAfterBodyItem() 280 } else { 281 endRange = p.PrevRange() 282 p.Read() // eat newline 283 } 284 } 285 } 286 287 return &Attribute{ 288 Name: string(ident.Bytes), 289 Expr: expr, 290 291 SrcRange: hcl.RangeBetween(ident.Range, endRange), 292 NameRange: ident.Range, 293 EqualsRange: eqTok.Range, 294 }, diags 295 } 296 297 func (p *parser) finishParsingBodyBlock(ident Token) (Node, hcl.Diagnostics) { 298 var blockType = string(ident.Bytes) 299 var diags hcl.Diagnostics 300 var labels []string 301 var labelRanges []hcl.Range 302 303 var oBrace Token 304 305 Token: 306 for { 307 tok := p.Peek() 308 309 switch tok.Type { 310 311 case TokenOBrace: 312 oBrace = p.Read() 313 break Token 314 315 case TokenOQuote: 316 label, labelRange, labelDiags := p.parseQuotedStringLiteral() 317 diags = append(diags, labelDiags...) 318 labels = append(labels, label) 319 labelRanges = append(labelRanges, labelRange) 320 // parseQuoteStringLiteral recovers up to the closing quote 321 // if it encounters problems, so we can continue looking for 322 // more labels and eventually the block body even. 323 324 case TokenIdent: 325 tok = p.Read() // eat token 326 label, labelRange := string(tok.Bytes), tok.Range 327 labels = append(labels, label) 328 labelRanges = append(labelRanges, labelRange) 329 330 default: 331 switch tok.Type { 332 case TokenEqual: 333 diags = append(diags, &hcl.Diagnostic{ 334 Severity: hcl.DiagError, 335 Summary: "Invalid block definition", 336 Detail: "The equals sign \"=\" indicates an argument definition, and must not be used when defining a block.", 337 Subject: &tok.Range, 338 Context: hcl.RangeBetween(ident.Range, tok.Range).Ptr(), 339 }) 340 case TokenNewline: 341 diags = append(diags, &hcl.Diagnostic{ 342 Severity: hcl.DiagError, 343 Summary: "Invalid block definition", 344 Detail: "A block definition must have block content delimited by \"{\" and \"}\", starting on the same line as the block header.", 345 Subject: &tok.Range, 346 Context: hcl.RangeBetween(ident.Range, tok.Range).Ptr(), 347 }) 348 default: 349 if !p.recovery { 350 diags = append(diags, &hcl.Diagnostic{ 351 Severity: hcl.DiagError, 352 Summary: "Invalid block definition", 353 Detail: "Either a quoted string block label or an opening brace (\"{\") is expected here.", 354 Subject: &tok.Range, 355 Context: hcl.RangeBetween(ident.Range, tok.Range).Ptr(), 356 }) 357 } 358 } 359 360 p.recoverAfterBodyItem() 361 362 return &Block{ 363 Type: blockType, 364 Labels: labels, 365 Body: &Body{ 366 SrcRange: ident.Range, 367 EndRange: ident.Range, 368 }, 369 370 TypeRange: ident.Range, 371 LabelRanges: labelRanges, 372 OpenBraceRange: ident.Range, // placeholder 373 CloseBraceRange: ident.Range, // placeholder 374 }, diags 375 } 376 } 377 378 // Once we fall out here, the peeker is pointed just after our opening 379 // brace, so we can begin our nested body parsing. 380 var body *Body 381 var bodyDiags hcl.Diagnostics 382 switch p.Peek().Type { 383 case TokenNewline, TokenEOF, TokenCBrace: 384 body, bodyDiags = p.ParseBody(TokenCBrace) 385 default: 386 // Special one-line, single-attribute block parsing mode. 387 body, bodyDiags = p.parseSingleAttrBody(TokenCBrace) 388 switch p.Peek().Type { 389 case TokenCBrace: 390 p.Read() // the happy path - just consume the closing brace 391 case TokenComma: 392 // User seems to be trying to use the object-constructor 393 // comma-separated style, which isn't permitted for blocks. 394 diags = append(diags, &hcl.Diagnostic{ 395 Severity: hcl.DiagError, 396 Summary: "Invalid single-argument block definition", 397 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.", 398 Subject: p.Peek().Range.Ptr(), 399 }) 400 p.recover(TokenCBrace) 401 case TokenNewline: 402 // We don't allow weird mixtures of single and multi-line syntax. 403 diags = append(diags, &hcl.Diagnostic{ 404 Severity: hcl.DiagError, 405 Summary: "Invalid single-argument block definition", 406 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.", 407 Subject: p.Peek().Range.Ptr(), 408 }) 409 p.recover(TokenCBrace) 410 default: 411 // Some other weird thing is going on. Since we can't guess a likely 412 // user intent for this one, we'll skip it if we're already in 413 // recovery mode. 414 if !p.recovery { 415 switch p.Peek().Type { 416 case TokenEOF: 417 diags = append(diags, &hcl.Diagnostic{ 418 Severity: hcl.DiagError, 419 Summary: "Unclosed configuration block", 420 Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", 421 Subject: oBrace.Range.Ptr(), 422 Context: hcl.RangeBetween(ident.Range, oBrace.Range).Ptr(), 423 }) 424 default: 425 diags = append(diags, &hcl.Diagnostic{ 426 Severity: hcl.DiagError, 427 Summary: "Invalid single-argument block definition", 428 Detail: "A single-line block definition must end with a closing brace immediately after its single argument definition.", 429 Subject: p.Peek().Range.Ptr(), 430 }) 431 } 432 } 433 p.recover(TokenCBrace) 434 } 435 } 436 diags = append(diags, bodyDiags...) 437 cBraceRange := p.PrevRange() 438 439 eol := p.Peek() 440 if eol.Type == TokenNewline || eol.Type == TokenEOF { 441 p.Read() // eat newline 442 } else { 443 if !p.recovery { 444 diags = append(diags, &hcl.Diagnostic{ 445 Severity: hcl.DiagError, 446 Summary: "Missing newline after block definition", 447 Detail: "A block definition must end with a newline.", 448 Subject: &eol.Range, 449 Context: hcl.RangeBetween(ident.Range, eol.Range).Ptr(), 450 }) 451 } 452 p.recoverAfterBodyItem() 453 } 454 455 // We must never produce a nil body, since the caller may attempt to 456 // do analysis of a partial result when there's an error, so we'll 457 // insert a placeholder if we otherwise failed to produce a valid 458 // body due to one of the syntax error paths above. 459 if body == nil && diags.HasErrors() { 460 body = &Body{ 461 SrcRange: hcl.RangeBetween(oBrace.Range, cBraceRange), 462 EndRange: cBraceRange, 463 } 464 } 465 466 return &Block{ 467 Type: blockType, 468 Labels: labels, 469 Body: body, 470 471 TypeRange: ident.Range, 472 LabelRanges: labelRanges, 473 OpenBraceRange: oBrace.Range, 474 CloseBraceRange: cBraceRange, 475 }, diags 476 } 477 478 func (p *parser) ParseExpression() (Expression, hcl.Diagnostics) { 479 return p.parseTernaryConditional() 480 } 481 482 func (p *parser) parseTernaryConditional() (Expression, hcl.Diagnostics) { 483 // The ternary conditional operator (.. ? .. : ..) behaves somewhat 484 // like a binary operator except that the "symbol" is itself 485 // an expression enclosed in two punctuation characters. 486 // The middle expression is parsed as if the ? and : symbols 487 // were parentheses. The "rhs" (the "false expression") is then 488 // treated right-associatively so it behaves similarly to the 489 // middle in terms of precedence. 490 491 startRange := p.NextRange() 492 var condExpr, trueExpr, falseExpr Expression 493 var diags hcl.Diagnostics 494 495 condExpr, condDiags := p.parseBinaryOps(binaryOps) 496 diags = append(diags, condDiags...) 497 if p.recovery && condDiags.HasErrors() { 498 return condExpr, diags 499 } 500 501 questionMark := p.Peek() 502 if questionMark.Type != TokenQuestion { 503 return condExpr, diags 504 } 505 506 p.Read() // eat question mark 507 508 trueExpr, trueDiags := p.ParseExpression() 509 diags = append(diags, trueDiags...) 510 if p.recovery && trueDiags.HasErrors() { 511 return condExpr, diags 512 } 513 514 colon := p.Peek() 515 if colon.Type != TokenColon { 516 diags = append(diags, &hcl.Diagnostic{ 517 Severity: hcl.DiagError, 518 Summary: "Missing false expression in conditional", 519 Detail: "The conditional operator (...?...:...) requires a false expression, delimited by a colon.", 520 Subject: &colon.Range, 521 Context: hcl.RangeBetween(startRange, colon.Range).Ptr(), 522 }) 523 return condExpr, diags 524 } 525 526 p.Read() // eat colon 527 528 falseExpr, falseDiags := p.ParseExpression() 529 diags = append(diags, falseDiags...) 530 if p.recovery && falseDiags.HasErrors() { 531 return condExpr, diags 532 } 533 534 return &ConditionalExpr{ 535 Condition: condExpr, 536 TrueResult: trueExpr, 537 FalseResult: falseExpr, 538 539 SrcRange: hcl.RangeBetween(startRange, falseExpr.Range()), 540 }, diags 541 } 542 543 // parseBinaryOps calls itself recursively to work through all of the 544 // operator precedence groups, and then eventually calls parseExpressionTerm 545 // for each operand. 546 func (p *parser) parseBinaryOps(ops []map[TokenType]*Operation) (Expression, hcl.Diagnostics) { 547 if len(ops) == 0 { 548 // We've run out of operators, so now we'll just try to parse a term. 549 return p.parseExpressionWithTraversals() 550 } 551 552 thisLevel := ops[0] 553 remaining := ops[1:] 554 555 var lhs, rhs Expression 556 var operation *Operation 557 var diags hcl.Diagnostics 558 559 // Parse a term that might be the first operand of a binary 560 // operation or it might just be a standalone term. 561 // We won't know until we've parsed it and can look ahead 562 // to see if there's an operator token for this level. 563 lhs, lhsDiags := p.parseBinaryOps(remaining) 564 diags = append(diags, lhsDiags...) 565 if p.recovery && lhsDiags.HasErrors() { 566 return lhs, diags 567 } 568 569 // We'll keep eating up operators until we run out, so that operators 570 // with the same precedence will combine in a left-associative manner: 571 // a+b+c => (a+b)+c, not a+(b+c) 572 // 573 // Should we later want to have right-associative operators, a way 574 // to achieve that would be to call back up to ParseExpression here 575 // instead of iteratively parsing only the remaining operators. 576 for { 577 next := p.Peek() 578 var newOp *Operation 579 var ok bool 580 if newOp, ok = thisLevel[next.Type]; !ok { 581 break 582 } 583 584 // Are we extending an expression started on the previous iteration? 585 if operation != nil { 586 lhs = &BinaryOpExpr{ 587 LHS: lhs, 588 Op: operation, 589 RHS: rhs, 590 591 SrcRange: hcl.RangeBetween(lhs.Range(), rhs.Range()), 592 } 593 } 594 595 operation = newOp 596 p.Read() // eat operator token 597 var rhsDiags hcl.Diagnostics 598 rhs, rhsDiags = p.parseBinaryOps(remaining) 599 diags = append(diags, rhsDiags...) 600 if p.recovery && rhsDiags.HasErrors() { 601 return lhs, diags 602 } 603 } 604 605 if operation == nil { 606 return lhs, diags 607 } 608 609 return &BinaryOpExpr{ 610 LHS: lhs, 611 Op: operation, 612 RHS: rhs, 613 614 SrcRange: hcl.RangeBetween(lhs.Range(), rhs.Range()), 615 }, diags 616 } 617 618 func (p *parser) parseExpressionWithTraversals() (Expression, hcl.Diagnostics) { 619 term, diags := p.parseExpressionTerm() 620 ret, moreDiags := p.parseExpressionTraversals(term) 621 diags = append(diags, moreDiags...) 622 return ret, diags 623 } 624 625 func (p *parser) parseExpressionTraversals(from Expression) (Expression, hcl.Diagnostics) { 626 var diags hcl.Diagnostics 627 ret := from 628 629 Traversal: 630 for { 631 next := p.Peek() 632 633 switch next.Type { 634 case TokenDot: 635 // Attribute access or splat 636 dot := p.Read() 637 attrTok := p.Peek() 638 639 switch attrTok.Type { 640 case TokenIdent: 641 attrTok = p.Read() // eat token 642 name := string(attrTok.Bytes) 643 rng := hcl.RangeBetween(dot.Range, attrTok.Range) 644 step := hcl.TraverseAttr{ 645 Name: name, 646 SrcRange: rng, 647 } 648 649 ret = makeRelativeTraversal(ret, step, rng) 650 651 case TokenNumberLit: 652 // This is a weird form we inherited from HIL, allowing numbers 653 // to be used as attributes as a weird way of writing [n]. 654 // This was never actually a first-class thing in HIL, but 655 // HIL tolerated sequences like .0. in its variable names and 656 // calling applications like Terraform exploited that to 657 // introduce indexing syntax where none existed. 658 numTok := p.Read() // eat token 659 attrTok = numTok 660 661 // This syntax is ambiguous if multiple indices are used in 662 // succession, like foo.0.1.baz: that actually parses as 663 // a fractional number 0.1. Since we're only supporting this 664 // syntax for compatibility with legacy Terraform 665 // configurations, and Terraform does not tend to have lists 666 // of lists, we'll choose to reject that here with a helpful 667 // error message, rather than failing later because the index 668 // isn't a whole number. 669 if dotIdx := bytes.IndexByte(numTok.Bytes, '.'); dotIdx >= 0 { 670 first := numTok.Bytes[:dotIdx] 671 second := numTok.Bytes[dotIdx+1:] 672 diags = append(diags, &hcl.Diagnostic{ 673 Severity: hcl.DiagError, 674 Summary: "Invalid legacy index syntax", 675 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), 676 Subject: &attrTok.Range, 677 }) 678 rng := hcl.RangeBetween(dot.Range, numTok.Range) 679 step := hcl.TraverseIndex{ 680 Key: cty.DynamicVal, 681 SrcRange: rng, 682 } 683 ret = makeRelativeTraversal(ret, step, rng) 684 break 685 } 686 687 numVal, numDiags := p.numberLitValue(numTok) 688 diags = append(diags, numDiags...) 689 690 rng := hcl.RangeBetween(dot.Range, numTok.Range) 691 step := hcl.TraverseIndex{ 692 Key: numVal, 693 SrcRange: rng, 694 } 695 696 ret = makeRelativeTraversal(ret, step, rng) 697 698 case TokenStar: 699 // "Attribute-only" splat expression. 700 // (This is a kinda weird construct inherited from HIL, which 701 // behaves a bit like a [*] splat except that it is only able 702 // to do attribute traversals into each of its elements, 703 // whereas foo[*] can support _any_ traversal. 704 marker := p.Read() // eat star 705 trav := make(hcl.Traversal, 0, 1) 706 var firstRange, lastRange hcl.Range 707 firstRange = p.NextRange() 708 lastRange = marker.Range 709 for p.Peek().Type == TokenDot { 710 dot := p.Read() 711 712 if p.Peek().Type == TokenNumberLit { 713 // Continuing the "weird stuff inherited from HIL" 714 // theme, we also allow numbers as attribute names 715 // inside splats and interpret them as indexing 716 // into a list, for expressions like: 717 // foo.bar.*.baz.0.foo 718 numTok := p.Read() 719 720 // Weird special case if the user writes something 721 // like foo.bar.*.baz.0.0.foo, where 0.0 parses 722 // as a number. 723 if dotIdx := bytes.IndexByte(numTok.Bytes, '.'); dotIdx >= 0 { 724 first := numTok.Bytes[:dotIdx] 725 second := numTok.Bytes[dotIdx+1:] 726 diags = append(diags, &hcl.Diagnostic{ 727 Severity: hcl.DiagError, 728 Summary: "Invalid legacy index syntax", 729 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), 730 Subject: &attrTok.Range, 731 }) 732 trav = append(trav, hcl.TraverseIndex{ 733 Key: cty.DynamicVal, 734 SrcRange: hcl.RangeBetween(dot.Range, numTok.Range), 735 }) 736 lastRange = numTok.Range 737 continue 738 } 739 740 numVal, numDiags := p.numberLitValue(numTok) 741 diags = append(diags, numDiags...) 742 trav = append(trav, hcl.TraverseIndex{ 743 Key: numVal, 744 SrcRange: hcl.RangeBetween(dot.Range, numTok.Range), 745 }) 746 lastRange = numTok.Range 747 continue 748 } 749 750 if p.Peek().Type != TokenIdent { 751 if !p.recovery { 752 if p.Peek().Type == TokenStar { 753 diags = append(diags, &hcl.Diagnostic{ 754 Severity: hcl.DiagError, 755 Summary: "Nested splat expression not allowed", 756 Detail: "A splat expression (*) cannot be used inside another attribute-only splat expression.", 757 Subject: p.Peek().Range.Ptr(), 758 }) 759 } else { 760 diags = append(diags, &hcl.Diagnostic{ 761 Severity: hcl.DiagError, 762 Summary: "Invalid attribute name", 763 Detail: "An attribute name is required after a dot.", 764 Subject: &attrTok.Range, 765 }) 766 } 767 } 768 p.setRecovery() 769 continue Traversal 770 } 771 772 attrTok := p.Read() 773 trav = append(trav, hcl.TraverseAttr{ 774 Name: string(attrTok.Bytes), 775 SrcRange: hcl.RangeBetween(dot.Range, attrTok.Range), 776 }) 777 lastRange = attrTok.Range 778 } 779 780 itemExpr := &AnonSymbolExpr{ 781 SrcRange: hcl.RangeBetween(dot.Range, marker.Range), 782 } 783 var travExpr Expression 784 if len(trav) == 0 { 785 travExpr = itemExpr 786 } else { 787 travExpr = &RelativeTraversalExpr{ 788 Source: itemExpr, 789 Traversal: trav, 790 SrcRange: hcl.RangeBetween(firstRange, lastRange), 791 } 792 } 793 794 ret = &SplatExpr{ 795 Source: ret, 796 Each: travExpr, 797 Item: itemExpr, 798 799 SrcRange: hcl.RangeBetween(from.Range(), lastRange), 800 MarkerRange: hcl.RangeBetween(dot.Range, marker.Range), 801 } 802 803 default: 804 diags = append(diags, &hcl.Diagnostic{ 805 Severity: hcl.DiagError, 806 Summary: "Invalid attribute name", 807 Detail: "An attribute name is required after a dot.", 808 Subject: &attrTok.Range, 809 }) 810 // This leaves the peeker in a bad place, so following items 811 // will probably be misparsed until we hit something that 812 // allows us to re-sync. 813 // 814 // We will probably need to do something better here eventually 815 // in order to support autocomplete triggered by typing a 816 // period. 817 p.setRecovery() 818 } 819 820 case TokenOBrack: 821 // Indexing of a collection. 822 // This may or may not be a hcl.Traverser, depending on whether 823 // the key value is something constant. 824 825 open := p.Read() 826 switch p.Peek().Type { 827 case TokenStar: 828 // This is a full splat expression, like foo[*], which consumes 829 // the rest of the traversal steps after it using a recursive 830 // call to this function. 831 p.Read() // consume star 832 close := p.Read() 833 if close.Type != TokenCBrack && !p.recovery { 834 diags = append(diags, &hcl.Diagnostic{ 835 Severity: hcl.DiagError, 836 Summary: "Missing close bracket on splat index", 837 Detail: "The star for a full splat operator must be immediately followed by a closing bracket (\"]\").", 838 Subject: &close.Range, 839 }) 840 close = p.recover(TokenCBrack) 841 } 842 // Splat expressions use a special "anonymous symbol" as a 843 // placeholder in an expression to be evaluated once for each 844 // item in the source expression. 845 itemExpr := &AnonSymbolExpr{ 846 SrcRange: hcl.RangeBetween(open.Range, close.Range), 847 } 848 // Now we'll recursively call this same function to eat any 849 // remaining traversal steps against the anonymous symbol. 850 travExpr, nestedDiags := p.parseExpressionTraversals(itemExpr) 851 diags = append(diags, nestedDiags...) 852 853 ret = &SplatExpr{ 854 Source: ret, 855 Each: travExpr, 856 Item: itemExpr, 857 858 SrcRange: hcl.RangeBetween(from.Range(), travExpr.Range()), 859 MarkerRange: hcl.RangeBetween(open.Range, close.Range), 860 } 861 862 default: 863 864 var close Token 865 p.PushIncludeNewlines(false) // arbitrary newlines allowed in brackets 866 keyExpr, keyDiags := p.ParseExpression() 867 diags = append(diags, keyDiags...) 868 if p.recovery && keyDiags.HasErrors() { 869 close = p.recover(TokenCBrack) 870 } else { 871 close = p.Read() 872 if close.Type != TokenCBrack && !p.recovery { 873 diags = append(diags, &hcl.Diagnostic{ 874 Severity: hcl.DiagError, 875 Summary: "Missing close bracket on index", 876 Detail: "The index operator must end with a closing bracket (\"]\").", 877 Subject: &close.Range, 878 }) 879 close = p.recover(TokenCBrack) 880 } 881 } 882 p.PopIncludeNewlines() 883 884 if lit, isLit := keyExpr.(*LiteralValueExpr); isLit { 885 litKey, _ := lit.Value(nil) 886 rng := hcl.RangeBetween(open.Range, close.Range) 887 step := hcl.TraverseIndex{ 888 Key: litKey, 889 SrcRange: rng, 890 } 891 ret = makeRelativeTraversal(ret, step, rng) 892 } else if tmpl, isTmpl := keyExpr.(*TemplateExpr); isTmpl && tmpl.IsStringLiteral() { 893 litKey, _ := tmpl.Value(nil) 894 rng := hcl.RangeBetween(open.Range, close.Range) 895 step := hcl.TraverseIndex{ 896 Key: litKey, 897 SrcRange: rng, 898 } 899 ret = makeRelativeTraversal(ret, step, rng) 900 } else { 901 rng := hcl.RangeBetween(open.Range, close.Range) 902 ret = &IndexExpr{ 903 Collection: ret, 904 Key: keyExpr, 905 906 SrcRange: hcl.RangeBetween(from.Range(), rng), 907 OpenRange: open.Range, 908 BracketRange: rng, 909 } 910 } 911 } 912 913 default: 914 break Traversal 915 } 916 } 917 918 return ret, diags 919 } 920 921 // makeRelativeTraversal takes an expression and a traverser and returns 922 // a traversal expression that combines the two. If the given expression 923 // is already a traversal, it is extended in place (mutating it) and 924 // returned. If it isn't, a new RelativeTraversalExpr is created and returned. 925 func makeRelativeTraversal(expr Expression, next hcl.Traverser, rng hcl.Range) Expression { 926 switch texpr := expr.(type) { 927 case *ScopeTraversalExpr: 928 texpr.Traversal = append(texpr.Traversal, next) 929 texpr.SrcRange = hcl.RangeBetween(texpr.SrcRange, rng) 930 return texpr 931 case *RelativeTraversalExpr: 932 texpr.Traversal = append(texpr.Traversal, next) 933 texpr.SrcRange = hcl.RangeBetween(texpr.SrcRange, rng) 934 return texpr 935 default: 936 return &RelativeTraversalExpr{ 937 Source: expr, 938 Traversal: hcl.Traversal{next}, 939 SrcRange: hcl.RangeBetween(expr.Range(), rng), 940 } 941 } 942 } 943 944 func (p *parser) parseExpressionTerm() (Expression, hcl.Diagnostics) { 945 start := p.Peek() 946 947 switch start.Type { 948 case TokenOParen: 949 oParen := p.Read() // eat open paren 950 951 p.PushIncludeNewlines(false) 952 953 expr, diags := p.ParseExpression() 954 if diags.HasErrors() { 955 // attempt to place the peeker after our closing paren 956 // before we return, so that the next parser has some 957 // chance of finding a valid expression. 958 p.recover(TokenCParen) 959 p.PopIncludeNewlines() 960 return expr, diags 961 } 962 963 close := p.Peek() 964 if close.Type != TokenCParen { 965 diags = append(diags, &hcl.Diagnostic{ 966 Severity: hcl.DiagError, 967 Summary: "Unbalanced parentheses", 968 Detail: "Expected a closing parenthesis to terminate the expression.", 969 Subject: &close.Range, 970 Context: hcl.RangeBetween(start.Range, close.Range).Ptr(), 971 }) 972 p.setRecovery() 973 } 974 975 cParen := p.Read() // eat closing paren 976 p.PopIncludeNewlines() 977 978 // Our parser's already taken care of the precedence effect of the 979 // parentheses by considering them to be a kind of "term", but we 980 // still need to include the parentheses in our AST so we can give 981 // an accurate representation of the source range that includes the 982 // open and closing parentheses. 983 expr = &ParenthesesExpr{ 984 Expression: expr, 985 SrcRange: hcl.RangeBetween(oParen.Range, cParen.Range), 986 } 987 988 return expr, diags 989 990 case TokenNumberLit: 991 tok := p.Read() // eat number token 992 993 numVal, diags := p.numberLitValue(tok) 994 return &LiteralValueExpr{ 995 Val: numVal, 996 SrcRange: tok.Range, 997 }, diags 998 999 case TokenIdent: 1000 tok := p.Read() // eat identifier token 1001 1002 if p.Peek().Type == TokenOParen || p.Peek().Type == TokenDoubleColon { 1003 return p.finishParsingFunctionCall(tok) 1004 } 1005 1006 name := string(tok.Bytes) 1007 switch name { 1008 case "true": 1009 return &LiteralValueExpr{ 1010 Val: cty.True, 1011 SrcRange: tok.Range, 1012 }, nil 1013 case "false": 1014 return &LiteralValueExpr{ 1015 Val: cty.False, 1016 SrcRange: tok.Range, 1017 }, nil 1018 case "null": 1019 return &LiteralValueExpr{ 1020 Val: cty.NullVal(cty.DynamicPseudoType), 1021 SrcRange: tok.Range, 1022 }, nil 1023 default: 1024 return &ScopeTraversalExpr{ 1025 Traversal: hcl.Traversal{ 1026 hcl.TraverseRoot{ 1027 Name: name, 1028 SrcRange: tok.Range, 1029 }, 1030 }, 1031 SrcRange: tok.Range, 1032 }, nil 1033 } 1034 1035 case TokenOQuote, TokenOHeredoc: 1036 open := p.Read() // eat opening marker 1037 closer := p.oppositeBracket(open.Type) 1038 exprs, passthru, _, diags := p.parseTemplateInner(closer, tokenOpensFlushHeredoc(open)) 1039 1040 closeRange := p.PrevRange() 1041 1042 if passthru { 1043 if len(exprs) != 1 { 1044 panic("passthru set with len(exprs) != 1") 1045 } 1046 return &TemplateWrapExpr{ 1047 Wrapped: exprs[0], 1048 SrcRange: hcl.RangeBetween(open.Range, closeRange), 1049 }, diags 1050 } 1051 1052 return &TemplateExpr{ 1053 Parts: exprs, 1054 SrcRange: hcl.RangeBetween(open.Range, closeRange), 1055 }, diags 1056 1057 case TokenMinus: 1058 tok := p.Read() // eat minus token 1059 1060 // Important to use parseExpressionWithTraversals rather than parseExpression 1061 // here, otherwise we can capture a following binary expression into 1062 // our negation. 1063 // e.g. -46+5 should parse as (-46)+5, not -(46+5) 1064 operand, diags := p.parseExpressionWithTraversals() 1065 return &UnaryOpExpr{ 1066 Op: OpNegate, 1067 Val: operand, 1068 1069 SrcRange: hcl.RangeBetween(tok.Range, operand.Range()), 1070 SymbolRange: tok.Range, 1071 }, diags 1072 1073 case TokenBang: 1074 tok := p.Read() // eat bang token 1075 1076 // Important to use parseExpressionWithTraversals rather than parseExpression 1077 // here, otherwise we can capture a following binary expression into 1078 // our negation. 1079 operand, diags := p.parseExpressionWithTraversals() 1080 return &UnaryOpExpr{ 1081 Op: OpLogicalNot, 1082 Val: operand, 1083 1084 SrcRange: hcl.RangeBetween(tok.Range, operand.Range()), 1085 SymbolRange: tok.Range, 1086 }, diags 1087 1088 case TokenOBrack: 1089 return p.parseTupleCons() 1090 1091 case TokenOBrace: 1092 return p.parseObjectCons() 1093 1094 default: 1095 var diags hcl.Diagnostics 1096 if !p.recovery { 1097 switch start.Type { 1098 case TokenEOF: 1099 diags = append(diags, &hcl.Diagnostic{ 1100 Severity: hcl.DiagError, 1101 Summary: "Missing expression", 1102 Detail: "Expected the start of an expression, but found the end of the file.", 1103 Subject: &start.Range, 1104 }) 1105 default: 1106 diags = append(diags, &hcl.Diagnostic{ 1107 Severity: hcl.DiagError, 1108 Summary: "Invalid expression", 1109 Detail: "Expected the start of an expression, but found an invalid expression token.", 1110 Subject: &start.Range, 1111 }) 1112 } 1113 } 1114 p.setRecovery() 1115 1116 // Return a placeholder so that the AST is still structurally sound 1117 // even in the presence of parse errors. 1118 return &LiteralValueExpr{ 1119 Val: cty.DynamicVal, 1120 SrcRange: start.Range, 1121 }, diags 1122 } 1123 } 1124 1125 func (p *parser) numberLitValue(tok Token) (cty.Value, hcl.Diagnostics) { 1126 // The cty.ParseNumberVal is always the same behavior as converting a 1127 // string to a number, ensuring we always interpret decimal numbers in 1128 // the same way. 1129 numVal, err := cty.ParseNumberVal(string(tok.Bytes)) 1130 if err != nil { 1131 ret := cty.UnknownVal(cty.Number) 1132 return ret, hcl.Diagnostics{ 1133 { 1134 Severity: hcl.DiagError, 1135 Summary: "Invalid number literal", 1136 // FIXME: not a very good error message, but convert only 1137 // gives us "a number is required", so not much help either. 1138 Detail: "Failed to recognize the value of this number literal.", 1139 Subject: &tok.Range, 1140 }, 1141 } 1142 } 1143 return numVal, nil 1144 } 1145 1146 // finishParsingFunctionCall parses a function call assuming that the function 1147 // name was already read, and so the peeker should be pointing at the opening 1148 // parenthesis after the name, or at the double-colon after the initial 1149 // function scope name. 1150 func (p *parser) finishParsingFunctionCall(name Token) (Expression, hcl.Diagnostics) { 1151 var diags hcl.Diagnostics 1152 1153 openTok := p.Read() 1154 if openTok.Type != TokenOParen && openTok.Type != TokenDoubleColon { 1155 // should never happen if callers behave 1156 panic("finishParsingFunctionCall called with unsupported next token") 1157 } 1158 1159 nameStr := string(name.Bytes) 1160 nameEndPos := name.Range.End 1161 for openTok.Type == TokenDoubleColon { 1162 nextName := p.Read() 1163 if nextName.Type != TokenIdent { 1164 diags = append(diags, &hcl.Diagnostic{ 1165 Severity: hcl.DiagError, 1166 Summary: "Missing function name", 1167 Detail: "Function scope resolution symbol :: must be followed by a function name in this scope.", 1168 Subject: &nextName.Range, 1169 Context: hcl.RangeBetween(name.Range, nextName.Range).Ptr(), 1170 }) 1171 p.recoverOver(TokenOParen) 1172 return nil, diags 1173 } 1174 1175 // Initial versions of HCLv2 didn't support function namespaces, and 1176 // so for backward compatibility we just treat namespaced functions 1177 // as weird names with "::" separators in them, saved as a string 1178 // to keep the API unchanged. FunctionCallExpr also has some special 1179 // handling of names containing :: when referring to a function that 1180 // doesn't exist in EvalContext, to return better error messages 1181 // when namespaces are used incorrectly. 1182 nameStr = nameStr + "::" + string(nextName.Bytes) 1183 nameEndPos = nextName.Range.End 1184 1185 openTok = p.Read() 1186 } 1187 1188 nameRange := hcl.Range{ 1189 Filename: name.Range.Filename, 1190 Start: name.Range.Start, 1191 End: nameEndPos, 1192 } 1193 1194 if openTok.Type != TokenOParen { 1195 diags = append(diags, &hcl.Diagnostic{ 1196 Severity: hcl.DiagError, 1197 Summary: "Missing open parenthesis", 1198 Detail: "Function selector must be followed by an open parenthesis to begin the function call.", 1199 Subject: &openTok.Range, 1200 Context: hcl.RangeBetween(name.Range, openTok.Range).Ptr(), 1201 }) 1202 p.recoverOver(TokenOParen) 1203 return nil, diags 1204 } 1205 1206 var args []Expression 1207 var expandFinal bool 1208 var closeTok Token 1209 1210 // Arbitrary newlines are allowed inside the function call parentheses. 1211 p.PushIncludeNewlines(false) 1212 1213 Token: 1214 for { 1215 tok := p.Peek() 1216 1217 if tok.Type == TokenCParen { 1218 closeTok = p.Read() // eat closing paren 1219 break Token 1220 } 1221 1222 arg, argDiags := p.ParseExpression() 1223 args = append(args, arg) 1224 diags = append(diags, argDiags...) 1225 if p.recovery && argDiags.HasErrors() { 1226 // if there was a parse error in the argument then we've 1227 // probably been left in a weird place in the token stream, 1228 // so we'll bail out with a partial argument list. 1229 recoveredTok := p.recover(TokenCParen) 1230 1231 // record the recovered token, if one was found 1232 if recoveredTok.Type == TokenCParen { 1233 closeTok = recoveredTok 1234 } 1235 break Token 1236 } 1237 1238 sep := p.Read() 1239 if sep.Type == TokenCParen { 1240 closeTok = sep 1241 break Token 1242 } 1243 1244 if sep.Type == TokenEllipsis { 1245 expandFinal = true 1246 1247 if p.Peek().Type != TokenCParen { 1248 if !p.recovery { 1249 diags = append(diags, &hcl.Diagnostic{ 1250 Severity: hcl.DiagError, 1251 Summary: "Missing closing parenthesis", 1252 Detail: "An expanded function argument (with ...) must be immediately followed by closing parentheses.", 1253 Subject: &sep.Range, 1254 Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(), 1255 }) 1256 } 1257 closeTok = p.recover(TokenCParen) 1258 } else { 1259 closeTok = p.Read() // eat closing paren 1260 } 1261 break Token 1262 } 1263 1264 if sep.Type != TokenComma { 1265 switch sep.Type { 1266 case TokenEOF: 1267 diags = append(diags, &hcl.Diagnostic{ 1268 Severity: hcl.DiagError, 1269 Summary: "Unterminated function call", 1270 Detail: "There is no closing parenthesis for this function call before the end of the file. This may be caused by incorrect parenthesis nesting elsewhere in this file.", 1271 Subject: hcl.RangeBetween(name.Range, openTok.Range).Ptr(), 1272 }) 1273 default: 1274 diags = append(diags, &hcl.Diagnostic{ 1275 Severity: hcl.DiagError, 1276 Summary: "Missing argument separator", 1277 Detail: "A comma is required to separate each function argument from the next.", 1278 Subject: &sep.Range, 1279 Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(), 1280 }) 1281 } 1282 closeTok = p.recover(TokenCParen) 1283 break Token 1284 } 1285 1286 if p.Peek().Type == TokenCParen { 1287 // A trailing comma after the last argument gets us in here. 1288 closeTok = p.Read() // eat closing paren 1289 break Token 1290 } 1291 1292 } 1293 1294 p.PopIncludeNewlines() 1295 1296 return &FunctionCallExpr{ 1297 Name: nameStr, 1298 Args: args, 1299 1300 ExpandFinal: expandFinal, 1301 1302 NameRange: nameRange, 1303 OpenParenRange: openTok.Range, 1304 CloseParenRange: closeTok.Range, 1305 }, diags 1306 } 1307 1308 func (p *parser) parseTupleCons() (Expression, hcl.Diagnostics) { 1309 open := p.Read() 1310 if open.Type != TokenOBrack { 1311 // Should never happen if callers are behaving 1312 panic("parseTupleCons called without peeker pointing to open bracket") 1313 } 1314 1315 p.PushIncludeNewlines(false) 1316 defer p.PopIncludeNewlines() 1317 1318 if forKeyword.TokenMatches(p.Peek()) { 1319 return p.finishParsingForExpr(open) 1320 } 1321 1322 var close Token 1323 1324 var diags hcl.Diagnostics 1325 var exprs []Expression 1326 1327 for { 1328 next := p.Peek() 1329 if next.Type == TokenCBrack { 1330 close = p.Read() // eat closer 1331 break 1332 } 1333 1334 expr, exprDiags := p.ParseExpression() 1335 exprs = append(exprs, expr) 1336 diags = append(diags, exprDiags...) 1337 1338 if p.recovery && exprDiags.HasErrors() { 1339 // If expression parsing failed then we are probably in a strange 1340 // place in the token stream, so we'll bail out and try to reset 1341 // to after our closing bracket to allow parsing to continue. 1342 close = p.recover(TokenCBrack) 1343 break 1344 } 1345 1346 next = p.Peek() 1347 if next.Type == TokenCBrack { 1348 close = p.Read() // eat closer 1349 break 1350 } 1351 1352 if next.Type != TokenComma { 1353 if !p.recovery { 1354 switch next.Type { 1355 case TokenEOF: 1356 diags = append(diags, &hcl.Diagnostic{ 1357 Severity: hcl.DiagError, 1358 Summary: "Unterminated tuple constructor expression", 1359 Detail: "There is no corresponding closing bracket before the end of the file. This may be caused by incorrect bracket nesting elsewhere in this file.", 1360 Subject: open.Range.Ptr(), 1361 }) 1362 default: 1363 diags = append(diags, &hcl.Diagnostic{ 1364 Severity: hcl.DiagError, 1365 Summary: "Missing item separator", 1366 Detail: "Expected a comma to mark the beginning of the next item.", 1367 Subject: &next.Range, 1368 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), 1369 }) 1370 } 1371 } 1372 close = p.recover(TokenCBrack) 1373 break 1374 } 1375 1376 p.Read() // eat comma 1377 1378 } 1379 1380 return &TupleConsExpr{ 1381 Exprs: exprs, 1382 1383 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1384 OpenRange: open.Range, 1385 }, diags 1386 } 1387 1388 func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { 1389 open := p.Read() 1390 if open.Type != TokenOBrace { 1391 // Should never happen if callers are behaving 1392 panic("parseObjectCons called without peeker pointing to open brace") 1393 } 1394 1395 // We must temporarily stop looking at newlines here while we check for 1396 // a "for" keyword, since for expressions are _not_ newline-sensitive, 1397 // even though object constructors are. 1398 p.PushIncludeNewlines(false) 1399 isFor := forKeyword.TokenMatches(p.Peek()) 1400 p.PopIncludeNewlines() 1401 if isFor { 1402 return p.finishParsingForExpr(open) 1403 } 1404 1405 p.PushIncludeNewlines(true) 1406 defer p.PopIncludeNewlines() 1407 1408 var close Token 1409 1410 var diags hcl.Diagnostics 1411 var items []ObjectConsItem 1412 1413 for { 1414 next := p.Peek() 1415 if next.Type == TokenNewline { 1416 p.Read() // eat newline 1417 continue 1418 } 1419 1420 if next.Type == TokenCBrace { 1421 close = p.Read() // eat closer 1422 break 1423 } 1424 1425 // Wrapping parens are not explicitly represented in the AST, but 1426 // we want to use them here to disambiguate intepreting a mapping 1427 // key as a full expression rather than just a name, and so 1428 // we'll remember this was present and use it to force the 1429 // behavior of our final ObjectConsKeyExpr. 1430 forceNonLiteral := (p.Peek().Type == TokenOParen) 1431 1432 var key Expression 1433 var keyDiags hcl.Diagnostics 1434 key, keyDiags = p.ParseExpression() 1435 diags = append(diags, keyDiags...) 1436 1437 if p.recovery && keyDiags.HasErrors() { 1438 // If expression parsing failed then we are probably in a strange 1439 // place in the token stream, so we'll bail out and try to reset 1440 // to after our closing brace to allow parsing to continue. 1441 close = p.recover(TokenCBrace) 1442 break 1443 } 1444 1445 // We wrap up the key expression in a special wrapper that deals 1446 // with our special case that naked identifiers as object keys 1447 // are interpreted as literal strings. 1448 key = &ObjectConsKeyExpr{ 1449 Wrapped: key, 1450 ForceNonLiteral: forceNonLiteral, 1451 } 1452 1453 next = p.Peek() 1454 if next.Type != TokenEqual && next.Type != TokenColon { 1455 if !p.recovery { 1456 switch next.Type { 1457 case TokenNewline, TokenComma: 1458 diags = append(diags, &hcl.Diagnostic{ 1459 Severity: hcl.DiagError, 1460 Summary: "Missing attribute value", 1461 Detail: "Expected an attribute value, introduced by an equals sign (\"=\").", 1462 Subject: &next.Range, 1463 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), 1464 }) 1465 case TokenIdent: 1466 // Although this might just be a plain old missing equals 1467 // sign before a reference, one way to get here is to try 1468 // to write an attribute name containing a period followed 1469 // by a digit, which was valid in HCL1, like this: 1470 // foo1.2_bar = "baz" 1471 // We can't know exactly what the user intended here, but 1472 // we'll augment our message with an extra hint in this case 1473 // in case it is helpful. 1474 diags = append(diags, &hcl.Diagnostic{ 1475 Severity: hcl.DiagError, 1476 Summary: "Missing key/value separator", 1477 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.", 1478 Subject: &next.Range, 1479 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), 1480 }) 1481 case TokenEOF: 1482 diags = append(diags, &hcl.Diagnostic{ 1483 Severity: hcl.DiagError, 1484 Summary: "Unterminated object constructor expression", 1485 Detail: "There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", 1486 Subject: open.Range.Ptr(), 1487 }) 1488 default: 1489 diags = append(diags, &hcl.Diagnostic{ 1490 Severity: hcl.DiagError, 1491 Summary: "Missing key/value separator", 1492 Detail: "Expected an equals sign (\"=\") to mark the beginning of the attribute value.", 1493 Subject: &next.Range, 1494 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), 1495 }) 1496 } 1497 } 1498 close = p.recover(TokenCBrace) 1499 break 1500 } 1501 1502 p.Read() // eat equals sign or colon 1503 1504 value, valueDiags := p.ParseExpression() 1505 diags = append(diags, valueDiags...) 1506 1507 if p.recovery && valueDiags.HasErrors() { 1508 // If expression parsing failed then we are probably in a strange 1509 // place in the token stream, so we'll bail out and try to reset 1510 // to after our closing brace to allow parsing to continue. 1511 close = p.recover(TokenCBrace) 1512 break 1513 } 1514 1515 items = append(items, ObjectConsItem{ 1516 KeyExpr: key, 1517 ValueExpr: value, 1518 }) 1519 1520 next = p.Peek() 1521 if next.Type == TokenCBrace { 1522 close = p.Read() // eat closer 1523 break 1524 } 1525 1526 if next.Type != TokenComma && next.Type != TokenNewline { 1527 if !p.recovery { 1528 switch next.Type { 1529 case TokenEOF: 1530 diags = append(diags, &hcl.Diagnostic{ 1531 Severity: hcl.DiagError, 1532 Summary: "Unterminated object constructor expression", 1533 Detail: "There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", 1534 Subject: open.Range.Ptr(), 1535 }) 1536 default: 1537 diags = append(diags, &hcl.Diagnostic{ 1538 Severity: hcl.DiagError, 1539 Summary: "Missing attribute separator", 1540 Detail: "Expected a newline or comma to mark the beginning of the next attribute.", 1541 Subject: &next.Range, 1542 Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), 1543 }) 1544 } 1545 } 1546 close = p.recover(TokenCBrace) 1547 break 1548 } 1549 1550 p.Read() // eat comma or newline 1551 1552 } 1553 1554 return &ObjectConsExpr{ 1555 Items: items, 1556 1557 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1558 OpenRange: open.Range, 1559 }, diags 1560 } 1561 1562 func (p *parser) finishParsingForExpr(open Token) (Expression, hcl.Diagnostics) { 1563 p.PushIncludeNewlines(false) 1564 defer p.PopIncludeNewlines() 1565 introducer := p.Read() 1566 if !forKeyword.TokenMatches(introducer) { 1567 // Should never happen if callers are behaving 1568 panic("finishParsingForExpr called without peeker pointing to 'for' identifier") 1569 } 1570 1571 var makeObj bool 1572 var closeType TokenType 1573 switch open.Type { 1574 case TokenOBrace: 1575 makeObj = true 1576 closeType = TokenCBrace 1577 case TokenOBrack: 1578 makeObj = false // making a tuple 1579 closeType = TokenCBrack 1580 default: 1581 // Should never happen if callers are behaving 1582 panic("finishParsingForExpr called with invalid open token") 1583 } 1584 1585 var diags hcl.Diagnostics 1586 var keyName, valName string 1587 1588 if p.Peek().Type != TokenIdent { 1589 if !p.recovery { 1590 diags = append(diags, &hcl.Diagnostic{ 1591 Severity: hcl.DiagError, 1592 Summary: "Invalid 'for' expression", 1593 Detail: "For expression requires variable name after 'for'.", 1594 Subject: p.Peek().Range.Ptr(), 1595 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(), 1596 }) 1597 } 1598 close := p.recover(closeType) 1599 return &LiteralValueExpr{ 1600 Val: cty.DynamicVal, 1601 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1602 }, diags 1603 } 1604 1605 valName = string(p.Read().Bytes) 1606 1607 if p.Peek().Type == TokenComma { 1608 // What we just read was actually the key, then. 1609 keyName = valName 1610 p.Read() // eat comma 1611 1612 if p.Peek().Type != TokenIdent { 1613 if !p.recovery { 1614 diags = append(diags, &hcl.Diagnostic{ 1615 Severity: hcl.DiagError, 1616 Summary: "Invalid 'for' expression", 1617 Detail: "For expression requires value variable name after comma.", 1618 Subject: p.Peek().Range.Ptr(), 1619 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(), 1620 }) 1621 } 1622 close := p.recover(closeType) 1623 return &LiteralValueExpr{ 1624 Val: cty.DynamicVal, 1625 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1626 }, diags 1627 } 1628 1629 valName = string(p.Read().Bytes) 1630 } 1631 1632 if !inKeyword.TokenMatches(p.Peek()) { 1633 if !p.recovery { 1634 diags = append(diags, &hcl.Diagnostic{ 1635 Severity: hcl.DiagError, 1636 Summary: "Invalid 'for' expression", 1637 Detail: "For expression requires the 'in' keyword after its name declarations.", 1638 Subject: p.Peek().Range.Ptr(), 1639 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(), 1640 }) 1641 } 1642 close := p.recover(closeType) 1643 return &LiteralValueExpr{ 1644 Val: cty.DynamicVal, 1645 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1646 }, diags 1647 } 1648 p.Read() // eat 'in' keyword 1649 1650 collExpr, collDiags := p.ParseExpression() 1651 diags = append(diags, collDiags...) 1652 if p.recovery && collDiags.HasErrors() { 1653 close := p.recover(closeType) 1654 return &LiteralValueExpr{ 1655 Val: cty.DynamicVal, 1656 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1657 }, diags 1658 } 1659 1660 if p.Peek().Type != TokenColon { 1661 if !p.recovery { 1662 diags = append(diags, &hcl.Diagnostic{ 1663 Severity: hcl.DiagError, 1664 Summary: "Invalid 'for' expression", 1665 Detail: "For expression requires a colon after the collection expression.", 1666 Subject: p.Peek().Range.Ptr(), 1667 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(), 1668 }) 1669 } 1670 close := p.recover(closeType) 1671 return &LiteralValueExpr{ 1672 Val: cty.DynamicVal, 1673 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1674 }, diags 1675 } 1676 p.Read() // eat colon 1677 1678 var keyExpr, valExpr Expression 1679 var keyDiags, valDiags hcl.Diagnostics 1680 valExpr, valDiags = p.ParseExpression() 1681 if p.Peek().Type == TokenFatArrow { 1682 // What we just parsed was actually keyExpr 1683 p.Read() // eat the fat arrow 1684 keyExpr, keyDiags = valExpr, valDiags 1685 1686 valExpr, valDiags = p.ParseExpression() 1687 } 1688 diags = append(diags, keyDiags...) 1689 diags = append(diags, valDiags...) 1690 if p.recovery && (keyDiags.HasErrors() || valDiags.HasErrors()) { 1691 close := p.recover(closeType) 1692 return &LiteralValueExpr{ 1693 Val: cty.DynamicVal, 1694 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1695 }, diags 1696 } 1697 1698 group := false 1699 var ellipsis Token 1700 if p.Peek().Type == TokenEllipsis { 1701 ellipsis = p.Read() 1702 group = true 1703 } 1704 1705 var condExpr Expression 1706 var condDiags hcl.Diagnostics 1707 if ifKeyword.TokenMatches(p.Peek()) { 1708 p.Read() // eat "if" 1709 condExpr, condDiags = p.ParseExpression() 1710 diags = append(diags, condDiags...) 1711 if p.recovery && condDiags.HasErrors() { 1712 close := p.recover(p.oppositeBracket(open.Type)) 1713 return &LiteralValueExpr{ 1714 Val: cty.DynamicVal, 1715 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1716 }, diags 1717 } 1718 } 1719 1720 var close Token 1721 if p.Peek().Type == closeType { 1722 close = p.Read() 1723 } else { 1724 if !p.recovery { 1725 diags = append(diags, &hcl.Diagnostic{ 1726 Severity: hcl.DiagError, 1727 Summary: "Invalid 'for' expression", 1728 Detail: "Extra characters after the end of the 'for' expression.", 1729 Subject: p.Peek().Range.Ptr(), 1730 Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(), 1731 }) 1732 } 1733 close = p.recover(closeType) 1734 } 1735 1736 if !makeObj { 1737 if keyExpr != nil { 1738 diags = append(diags, &hcl.Diagnostic{ 1739 Severity: hcl.DiagError, 1740 Summary: "Invalid 'for' expression", 1741 Detail: "Key expression is not valid when building a tuple.", 1742 Subject: keyExpr.Range().Ptr(), 1743 Context: hcl.RangeBetween(open.Range, close.Range).Ptr(), 1744 }) 1745 } 1746 1747 if group { 1748 diags = append(diags, &hcl.Diagnostic{ 1749 Severity: hcl.DiagError, 1750 Summary: "Invalid 'for' expression", 1751 Detail: "Grouping ellipsis (...) cannot be used when building a tuple.", 1752 Subject: &ellipsis.Range, 1753 Context: hcl.RangeBetween(open.Range, close.Range).Ptr(), 1754 }) 1755 } 1756 } else { 1757 if keyExpr == nil { 1758 diags = append(diags, &hcl.Diagnostic{ 1759 Severity: hcl.DiagError, 1760 Summary: "Invalid 'for' expression", 1761 Detail: "Key expression is required when building an object.", 1762 Subject: valExpr.Range().Ptr(), 1763 Context: hcl.RangeBetween(open.Range, close.Range).Ptr(), 1764 }) 1765 } 1766 } 1767 1768 return &ForExpr{ 1769 KeyVar: keyName, 1770 ValVar: valName, 1771 CollExpr: collExpr, 1772 KeyExpr: keyExpr, 1773 ValExpr: valExpr, 1774 CondExpr: condExpr, 1775 Group: group, 1776 1777 SrcRange: hcl.RangeBetween(open.Range, close.Range), 1778 OpenRange: open.Range, 1779 CloseRange: close.Range, 1780 }, diags 1781 } 1782 1783 // parseQuotedStringLiteral is a helper for parsing quoted strings that 1784 // aren't allowed to contain any interpolations, such as block labels. 1785 func (p *parser) parseQuotedStringLiteral() (string, hcl.Range, hcl.Diagnostics) { 1786 oQuote := p.Read() 1787 if oQuote.Type != TokenOQuote { 1788 return "", oQuote.Range, hcl.Diagnostics{ 1789 { 1790 Severity: hcl.DiagError, 1791 Summary: "Invalid string literal", 1792 Detail: "A quoted string is required here.", 1793 Subject: &oQuote.Range, 1794 }, 1795 } 1796 } 1797 1798 var diags hcl.Diagnostics 1799 ret := &bytes.Buffer{} 1800 var endRange hcl.Range 1801 1802 Token: 1803 for { 1804 tok := p.Read() 1805 switch tok.Type { 1806 1807 case TokenCQuote: 1808 endRange = tok.Range 1809 break Token 1810 1811 case TokenQuotedLit: 1812 s, sDiags := ParseStringLiteralToken(tok) 1813 diags = append(diags, sDiags...) 1814 ret.WriteString(s) 1815 1816 case TokenTemplateControl, TokenTemplateInterp: 1817 which := "$" 1818 if tok.Type == TokenTemplateControl { 1819 which = "%" 1820 } 1821 1822 diags = append(diags, &hcl.Diagnostic{ 1823 Severity: hcl.DiagError, 1824 Summary: "Invalid string literal", 1825 Detail: fmt.Sprintf( 1826 "Template sequences are not allowed in this string. To include a literal %q, double it (as \"%s%s\") to escape it.", 1827 which, which, which, 1828 ), 1829 Subject: &tok.Range, 1830 Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(), 1831 }) 1832 1833 // Now that we're returning an error callers won't attempt to use 1834 // the result for any real operations, but they might try to use 1835 // the partial AST for other analyses, so we'll leave a marker 1836 // to indicate that there was something invalid in the string to 1837 // help avoid misinterpretation of the partial result 1838 ret.WriteString(which) 1839 ret.WriteString("{ ... }") 1840 1841 p.recover(TokenTemplateSeqEnd) // we'll try to keep parsing after the sequence ends 1842 1843 case TokenEOF: 1844 diags = append(diags, &hcl.Diagnostic{ 1845 Severity: hcl.DiagError, 1846 Summary: "Unterminated string literal", 1847 Detail: "Unable to find the closing quote mark before the end of the file.", 1848 Subject: &tok.Range, 1849 Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(), 1850 }) 1851 endRange = tok.Range 1852 break Token 1853 1854 default: 1855 // Should never happen, as long as the scanner is behaving itself 1856 diags = append(diags, &hcl.Diagnostic{ 1857 Severity: hcl.DiagError, 1858 Summary: "Invalid string literal", 1859 Detail: "This item is not valid in a string literal.", 1860 Subject: &tok.Range, 1861 Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(), 1862 }) 1863 p.recover(TokenCQuote) 1864 endRange = tok.Range 1865 break Token 1866 1867 } 1868 1869 } 1870 1871 return ret.String(), hcl.RangeBetween(oQuote.Range, endRange), diags 1872 } 1873 1874 // ParseStringLiteralToken processes the given token, which must be either a 1875 // TokenQuotedLit or a TokenStringLit, returning the string resulting from 1876 // resolving any escape sequences. 1877 // 1878 // If any error diagnostics are returned, the returned string may be incomplete 1879 // or otherwise invalid. 1880 func ParseStringLiteralToken(tok Token) (string, hcl.Diagnostics) { 1881 var quoted bool 1882 switch tok.Type { 1883 case TokenQuotedLit: 1884 quoted = true 1885 case TokenStringLit: 1886 quoted = false 1887 default: 1888 panic("ParseStringLiteralToken can only be used with TokenStringLit and TokenQuotedLit tokens") 1889 } 1890 var diags hcl.Diagnostics 1891 1892 ret := make([]byte, 0, len(tok.Bytes)) 1893 slices := scanStringLit(tok.Bytes, quoted) 1894 1895 // We will mutate rng constantly as we walk through our token slices below. 1896 // Any diagnostics must take a copy of this rng rather than simply pointing 1897 // to it, e.g. by using rng.Ptr() rather than &rng. 1898 rng := tok.Range 1899 rng.End = rng.Start 1900 1901 Slices: 1902 for _, slice := range slices { 1903 if len(slice) == 0 { 1904 continue 1905 } 1906 1907 // Advance the start of our range to where the previous token ended 1908 rng.Start = rng.End 1909 1910 // Advance the end of our range to after our token. 1911 b := slice 1912 for len(b) > 0 { 1913 adv, ch, _ := textseg.ScanGraphemeClusters(b, true) 1914 rng.End.Byte += adv 1915 switch ch[0] { 1916 case '\r', '\n': 1917 rng.End.Line++ 1918 rng.End.Column = 1 1919 default: 1920 rng.End.Column++ 1921 } 1922 b = b[adv:] 1923 } 1924 1925 TokenType: 1926 switch slice[0] { 1927 case '\\': 1928 if !quoted { 1929 // If we're not in quoted mode then just treat this token as 1930 // normal. (Slices can still start with backslash even if we're 1931 // not specifically looking for backslash sequences.) 1932 break TokenType 1933 } 1934 if len(slice) < 2 { 1935 diags = append(diags, &hcl.Diagnostic{ 1936 Severity: hcl.DiagError, 1937 Summary: "Invalid escape sequence", 1938 Detail: "Backslash must be followed by an escape sequence selector character.", 1939 Subject: rng.Ptr(), 1940 }) 1941 break TokenType 1942 } 1943 1944 switch slice[1] { 1945 1946 case 'n': 1947 ret = append(ret, '\n') 1948 continue Slices 1949 case 'r': 1950 ret = append(ret, '\r') 1951 continue Slices 1952 case 't': 1953 ret = append(ret, '\t') 1954 continue Slices 1955 case '"': 1956 ret = append(ret, '"') 1957 continue Slices 1958 case '\\': 1959 ret = append(ret, '\\') 1960 continue Slices 1961 case 'u', 'U': 1962 if slice[1] == 'u' && len(slice) != 6 { 1963 diags = append(diags, &hcl.Diagnostic{ 1964 Severity: hcl.DiagError, 1965 Summary: "Invalid escape sequence", 1966 Detail: "The \\u escape sequence must be followed by four hexadecimal digits.", 1967 Subject: rng.Ptr(), 1968 }) 1969 break TokenType 1970 } else if slice[1] == 'U' && len(slice) != 10 { 1971 diags = append(diags, &hcl.Diagnostic{ 1972 Severity: hcl.DiagError, 1973 Summary: "Invalid escape sequence", 1974 Detail: "The \\U escape sequence must be followed by eight hexadecimal digits.", 1975 Subject: rng.Ptr(), 1976 }) 1977 break TokenType 1978 } 1979 1980 numHex := string(slice[2:]) 1981 num, err := strconv.ParseUint(numHex, 16, 32) 1982 if err != nil { 1983 // Should never happen because the scanner won't match 1984 // a sequence of digits that isn't valid. 1985 panic(err) 1986 } 1987 1988 r := rune(num) 1989 l := utf8.RuneLen(r) 1990 if l == -1 { 1991 diags = append(diags, &hcl.Diagnostic{ 1992 Severity: hcl.DiagError, 1993 Summary: "Invalid escape sequence", 1994 Detail: fmt.Sprintf("Cannot encode character U+%04x in UTF-8.", num), 1995 Subject: rng.Ptr(), 1996 }) 1997 break TokenType 1998 } 1999 for i := 0; i < l; i++ { 2000 ret = append(ret, 0) 2001 } 2002 rb := ret[len(ret)-l:] 2003 utf8.EncodeRune(rb, r) 2004 2005 continue Slices 2006 2007 default: 2008 diags = append(diags, &hcl.Diagnostic{ 2009 Severity: hcl.DiagError, 2010 Summary: "Invalid escape sequence", 2011 Detail: fmt.Sprintf("The symbol %q is not a valid escape sequence selector.", slice[1:]), 2012 Subject: rng.Ptr(), 2013 }) 2014 ret = append(ret, slice[1:]...) 2015 continue Slices 2016 } 2017 2018 case '$', '%': 2019 if len(slice) != 3 { 2020 // Not long enough to be our escape sequence, so it's literal. 2021 break TokenType 2022 } 2023 2024 if slice[1] == slice[0] && slice[2] == '{' { 2025 ret = append(ret, slice[0]) 2026 ret = append(ret, '{') 2027 continue Slices 2028 } 2029 2030 break TokenType 2031 } 2032 2033 // If we fall out here or break out of here from the switch above 2034 // then this slice is just a literal. 2035 ret = append(ret, slice...) 2036 } 2037 2038 return string(ret), diags 2039 } 2040 2041 // setRecovery turns on recovery mode without actually doing any recovery. 2042 // This can be used when a parser knowingly leaves the peeker in a useless 2043 // place and wants to suppress errors that might result from that decision. 2044 func (p *parser) setRecovery() { 2045 p.recovery = true 2046 } 2047 2048 // recover seeks forward in the token stream until it finds TokenType "end", 2049 // then returns with the peeker pointed at the following token. 2050 // 2051 // If the given token type is a bracketer, this function will additionally 2052 // count nested instances of the brackets to try to leave the peeker at 2053 // the end of the _current_ instance of that bracketer, skipping over any 2054 // nested instances. This is a best-effort operation and may have 2055 // unpredictable results on input with bad bracketer nesting. 2056 func (p *parser) recover(end TokenType) Token { 2057 start := p.oppositeBracket(end) 2058 p.recovery = true 2059 2060 nest := 0 2061 for { 2062 tok := p.Read() 2063 ty := tok.Type 2064 if end == TokenTemplateSeqEnd && ty == TokenTemplateControl { 2065 // normalize so that our matching behavior can work, since 2066 // TokenTemplateControl/TokenTemplateInterp are asymmetrical 2067 // with TokenTemplateSeqEnd and thus we need to count both 2068 // openers if that's the closer we're looking for. 2069 ty = TokenTemplateInterp 2070 } 2071 2072 switch ty { 2073 case start: 2074 nest++ 2075 case end: 2076 if nest < 1 { 2077 return tok 2078 } 2079 2080 nest-- 2081 case TokenEOF: 2082 return tok 2083 } 2084 } 2085 } 2086 2087 // recoverOver seeks forward in the token stream until it finds a block 2088 // starting with TokenType "start", then finds the corresponding end token, 2089 // leaving the peeker pointed at the token after that end token. 2090 // 2091 // The given token type _must_ be a bracketer. For example, if the given 2092 // start token is TokenOBrace then the parser will be left at the _end_ of 2093 // the next brace-delimited block encountered, or at EOF if no such block 2094 // is found or it is unclosed. 2095 func (p *parser) recoverOver(start TokenType) { 2096 end := p.oppositeBracket(start) 2097 2098 // find the opening bracket first 2099 Token: 2100 for { 2101 tok := p.Read() 2102 switch tok.Type { 2103 case start, TokenEOF: 2104 break Token 2105 } 2106 } 2107 2108 // Now use our existing recover function to locate the _end_ of the 2109 // container we've found. 2110 p.recover(end) 2111 } 2112 2113 func (p *parser) recoverAfterBodyItem() { 2114 p.recovery = true 2115 var open []TokenType 2116 2117 Token: 2118 for { 2119 tok := p.Read() 2120 2121 switch tok.Type { 2122 2123 case TokenNewline: 2124 if len(open) == 0 { 2125 break Token 2126 } 2127 2128 case TokenEOF: 2129 break Token 2130 2131 case TokenOBrace, TokenOBrack, TokenOParen, TokenOQuote, TokenOHeredoc, TokenTemplateInterp, TokenTemplateControl: 2132 open = append(open, tok.Type) 2133 2134 case TokenCBrace, TokenCBrack, TokenCParen, TokenCQuote, TokenCHeredoc: 2135 opener := p.oppositeBracket(tok.Type) 2136 for len(open) > 0 && open[len(open)-1] != opener { 2137 open = open[:len(open)-1] 2138 } 2139 if len(open) > 0 { 2140 open = open[:len(open)-1] 2141 } 2142 2143 case TokenTemplateSeqEnd: 2144 for len(open) > 0 && open[len(open)-1] != TokenTemplateInterp && open[len(open)-1] != TokenTemplateControl { 2145 open = open[:len(open)-1] 2146 } 2147 if len(open) > 0 { 2148 open = open[:len(open)-1] 2149 } 2150 2151 } 2152 } 2153 } 2154 2155 // oppositeBracket finds the bracket that opposes the given bracketer, or 2156 // NilToken if the given token isn't a bracketer. 2157 // 2158 // "Bracketer", for the sake of this function, is one end of a matching 2159 // open/close set of tokens that establish a bracketing context. 2160 func (p *parser) oppositeBracket(ty TokenType) TokenType { 2161 switch ty { 2162 2163 case TokenOBrace: 2164 return TokenCBrace 2165 case TokenOBrack: 2166 return TokenCBrack 2167 case TokenOParen: 2168 return TokenCParen 2169 case TokenOQuote: 2170 return TokenCQuote 2171 case TokenOHeredoc: 2172 return TokenCHeredoc 2173 2174 case TokenCBrace: 2175 return TokenOBrace 2176 case TokenCBrack: 2177 return TokenOBrack 2178 case TokenCParen: 2179 return TokenOParen 2180 case TokenCQuote: 2181 return TokenOQuote 2182 case TokenCHeredoc: 2183 return TokenOHeredoc 2184 2185 case TokenTemplateControl: 2186 return TokenTemplateSeqEnd 2187 case TokenTemplateInterp: 2188 return TokenTemplateSeqEnd 2189 case TokenTemplateSeqEnd: 2190 // This is ambigous, but we return Interp here because that's 2191 // what's assumed by the "recover" method. 2192 return TokenTemplateInterp 2193 2194 default: 2195 return TokenNil 2196 } 2197 } 2198 2199 func errPlaceholderExpr(rng hcl.Range) Expression { 2200 return &LiteralValueExpr{ 2201 Val: cty.DynamicVal, 2202 SrcRange: rng, 2203 } 2204 }