diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go | 388 |
1 files changed, 294 insertions, 94 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go index 002858f..253ad50 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go | |||
@@ -9,7 +9,6 @@ import ( | |||
9 | "github.com/apparentlymart/go-textseg/textseg" | 9 | "github.com/apparentlymart/go-textseg/textseg" |
10 | "github.com/hashicorp/hcl2/hcl" | 10 | "github.com/hashicorp/hcl2/hcl" |
11 | "github.com/zclconf/go-cty/cty" | 11 | "github.com/zclconf/go-cty/cty" |
12 | "github.com/zclconf/go-cty/cty/convert" | ||
13 | ) | 12 | ) |
14 | 13 | ||
15 | type parser struct { | 14 | type parser struct { |
@@ -55,7 +54,7 @@ Token: | |||
55 | Severity: hcl.DiagError, | 54 | Severity: hcl.DiagError, |
56 | Summary: "Attribute redefined", | 55 | Summary: "Attribute redefined", |
57 | Detail: fmt.Sprintf( | 56 | Detail: fmt.Sprintf( |
58 | "The attribute %q was already defined at %s. Each attribute may be defined only once.", | 57 | "The argument %q was already set at %s. Each argument may be set only once.", |
59 | titem.Name, existing.NameRange.String(), | 58 | titem.Name, existing.NameRange.String(), |
60 | ), | 59 | ), |
61 | Subject: &titem.NameRange, | 60 | Subject: &titem.NameRange, |
@@ -80,15 +79,15 @@ Token: | |||
80 | if bad.Type == TokenOQuote { | 79 | if bad.Type == TokenOQuote { |
81 | diags = append(diags, &hcl.Diagnostic{ | 80 | diags = append(diags, &hcl.Diagnostic{ |
82 | Severity: hcl.DiagError, | 81 | Severity: hcl.DiagError, |
83 | Summary: "Invalid attribute name", | 82 | Summary: "Invalid argument name", |
84 | Detail: "Attribute names must not be quoted.", | 83 | Detail: "Argument names must not be quoted.", |
85 | Subject: &bad.Range, | 84 | Subject: &bad.Range, |
86 | }) | 85 | }) |
87 | } else { | 86 | } else { |
88 | diags = append(diags, &hcl.Diagnostic{ | 87 | diags = append(diags, &hcl.Diagnostic{ |
89 | Severity: hcl.DiagError, | 88 | Severity: hcl.DiagError, |
90 | Summary: "Attribute or block definition required", | 89 | Summary: "Argument or block definition required", |
91 | Detail: "An attribute or block definition is required here.", | 90 | Detail: "An argument or block definition is required here.", |
92 | Subject: &bad.Range, | 91 | Subject: &bad.Range, |
93 | }) | 92 | }) |
94 | } | 93 | } |
@@ -120,8 +119,8 @@ func (p *parser) ParseBodyItem() (Node, hcl.Diagnostics) { | |||
120 | return nil, hcl.Diagnostics{ | 119 | return nil, hcl.Diagnostics{ |
121 | { | 120 | { |
122 | Severity: hcl.DiagError, | 121 | Severity: hcl.DiagError, |
123 | Summary: "Attribute or block definition required", | 122 | Summary: "Argument or block definition required", |
124 | Detail: "An attribute or block definition is required here.", | 123 | Detail: "An argument or block definition is required here.", |
125 | Subject: &ident.Range, | 124 | Subject: &ident.Range, |
126 | }, | 125 | }, |
127 | } | 126 | } |
@@ -131,7 +130,7 @@ func (p *parser) ParseBodyItem() (Node, hcl.Diagnostics) { | |||
131 | 130 | ||
132 | switch next.Type { | 131 | switch next.Type { |
133 | case TokenEqual: | 132 | case TokenEqual: |
134 | return p.finishParsingBodyAttribute(ident) | 133 | return p.finishParsingBodyAttribute(ident, false) |
135 | case TokenOQuote, TokenOBrace, TokenIdent: | 134 | case TokenOQuote, TokenOBrace, TokenIdent: |
136 | return p.finishParsingBodyBlock(ident) | 135 | return p.finishParsingBodyBlock(ident) |
137 | default: | 136 | default: |
@@ -139,8 +138,8 @@ func (p *parser) ParseBodyItem() (Node, hcl.Diagnostics) { | |||
139 | return nil, hcl.Diagnostics{ | 138 | return nil, hcl.Diagnostics{ |
140 | { | 139 | { |
141 | Severity: hcl.DiagError, | 140 | Severity: hcl.DiagError, |
142 | Summary: "Attribute or block definition required", | 141 | Summary: "Argument or block definition required", |
143 | Detail: "An attribute or block definition is required here. To define an attribute, use the equals sign \"=\" to introduce the attribute value.", | 142 | Detail: "An argument or block definition is required here. To set an argument, use the equals sign \"=\" to introduce the argument value.", |
144 | Subject: &ident.Range, | 143 | Subject: &ident.Range, |
145 | }, | 144 | }, |
146 | } | 145 | } |
@@ -149,7 +148,72 @@ func (p *parser) ParseBodyItem() (Node, hcl.Diagnostics) { | |||
149 | return nil, nil | 148 | return nil, nil |
150 | } | 149 | } |
151 | 150 | ||
152 | func (p *parser) finishParsingBodyAttribute(ident Token) (Node, hcl.Diagnostics) { | 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) { | ||
156 | ident := p.Read() | ||
157 | if ident.Type != TokenIdent { | ||
158 | p.recoverAfterBodyItem() | ||
159 | return nil, hcl.Diagnostics{ | ||
160 | { | ||
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, | ||
165 | }, | ||
166 | } | ||
167 | } | ||
168 | |||
169 | var attr *Attribute | ||
170 | var diags hcl.Diagnostics | ||
171 | |||
172 | next := p.Peek() | ||
173 | |||
174 | switch next.Type { | ||
175 | case TokenEqual: | ||
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{ | ||
182 | { | ||
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(), | ||
187 | }, | ||
188 | } | ||
189 | default: | ||
190 | p.recoverAfterBodyItem() | ||
191 | return nil, hcl.Diagnostics{ | ||
192 | { | ||
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, | ||
197 | }, | ||
198 | } | ||
199 | } | ||
200 | |||
201 | return &Body{ | ||
202 | Attributes: Attributes{ | ||
203 | string(ident.Bytes): attr, | ||
204 | }, | ||
205 | |||
206 | SrcRange: attr.SrcRange, | ||
207 | EndRange: hcl.Range{ | ||
208 | Filename: attr.SrcRange.Filename, | ||
209 | Start: attr.SrcRange.End, | ||
210 | End: attr.SrcRange.End, | ||
211 | }, | ||
212 | }, diags | ||
213 | |||
214 | } | ||
215 | |||
216 | func (p *parser) finishParsingBodyAttribute(ident Token, singleLine bool) (Node, hcl.Diagnostics) { | ||
153 | eqTok := p.Read() // eat equals token | 217 | eqTok := p.Read() // eat equals token |
154 | if eqTok.Type != TokenEqual { | 218 | if eqTok.Type != TokenEqual { |
155 | // should never happen if caller behaves | 219 | // should never happen if caller behaves |
@@ -166,22 +230,33 @@ func (p *parser) finishParsingBodyAttribute(ident Token) (Node, hcl.Diagnostics) | |||
166 | endRange = p.PrevRange() | 230 | endRange = p.PrevRange() |
167 | p.recoverAfterBodyItem() | 231 | p.recoverAfterBodyItem() |
168 | } else { | 232 | } else { |
169 | end := p.Peek() | 233 | endRange = p.PrevRange() |
170 | if end.Type != TokenNewline && end.Type != TokenEOF { | 234 | if !singleLine { |
171 | if !p.recovery { | 235 | end := p.Peek() |
172 | diags = append(diags, &hcl.Diagnostic{ | 236 | if end.Type != TokenNewline && end.Type != TokenEOF { |
173 | Severity: hcl.DiagError, | 237 | if !p.recovery { |
174 | Summary: "Missing newline after attribute definition", | 238 | summary := "Missing newline after argument" |
175 | Detail: "An attribute definition must end with a newline.", | 239 | detail := "An argument definition must end with a newline." |
176 | Subject: &end.Range, | 240 | |
177 | Context: hcl.RangeBetween(ident.Range, end.Range).Ptr(), | 241 | if end.Type == TokenComma { |
178 | }) | 242 | summary = "Unexpected comma after argument" |
243 | detail = "Argument definitions must be separated by newlines, not commas. " + detail | ||
244 | } | ||
245 | |||
246 | diags = append(diags, &hcl.Diagnostic{ | ||
247 | Severity: hcl.DiagError, | ||
248 | Summary: summary, | ||
249 | Detail: detail, | ||
250 | Subject: &end.Range, | ||
251 | Context: hcl.RangeBetween(ident.Range, end.Range).Ptr(), | ||
252 | }) | ||
253 | } | ||
254 | endRange = p.PrevRange() | ||
255 | p.recoverAfterBodyItem() | ||
256 | } else { | ||
257 | endRange = p.PrevRange() | ||
258 | p.Read() // eat newline | ||
179 | } | 259 | } |
180 | endRange = p.PrevRange() | ||
181 | p.recoverAfterBodyItem() | ||
182 | } else { | ||
183 | endRange = p.PrevRange() | ||
184 | p.Read() // eat newline | ||
185 | } | 260 | } |
186 | } | 261 | } |
187 | 262 | ||
@@ -218,19 +293,9 @@ Token: | |||
218 | diags = append(diags, labelDiags...) | 293 | diags = append(diags, labelDiags...) |
219 | labels = append(labels, label) | 294 | labels = append(labels, label) |
220 | labelRanges = append(labelRanges, labelRange) | 295 | labelRanges = append(labelRanges, labelRange) |
221 | if labelDiags.HasErrors() { | 296 | // parseQuoteStringLiteral recovers up to the closing quote |
222 | p.recoverAfterBodyItem() | 297 | // if it encounters problems, so we can continue looking for |
223 | return &Block{ | 298 | // more labels and eventually the block body even. |
224 | Type: blockType, | ||
225 | Labels: labels, | ||
226 | Body: nil, | ||
227 | |||
228 | TypeRange: ident.Range, | ||
229 | LabelRanges: labelRanges, | ||
230 | OpenBraceRange: ident.Range, // placeholder | ||
231 | CloseBraceRange: ident.Range, // placeholder | ||
232 | }, diags | ||
233 | } | ||
234 | 299 | ||
235 | case TokenIdent: | 300 | case TokenIdent: |
236 | tok = p.Read() // eat token | 301 | tok = p.Read() // eat token |
@@ -244,7 +309,7 @@ Token: | |||
244 | diags = append(diags, &hcl.Diagnostic{ | 309 | diags = append(diags, &hcl.Diagnostic{ |
245 | Severity: hcl.DiagError, | 310 | Severity: hcl.DiagError, |
246 | Summary: "Invalid block definition", | 311 | Summary: "Invalid block definition", |
247 | Detail: "The equals sign \"=\" indicates an attribute definition, and must not be used when defining a block.", | 312 | Detail: "The equals sign \"=\" indicates an argument definition, and must not be used when defining a block.", |
248 | Subject: &tok.Range, | 313 | Subject: &tok.Range, |
249 | Context: hcl.RangeBetween(ident.Range, tok.Range).Ptr(), | 314 | Context: hcl.RangeBetween(ident.Range, tok.Range).Ptr(), |
250 | }) | 315 | }) |
@@ -273,7 +338,10 @@ Token: | |||
273 | return &Block{ | 338 | return &Block{ |
274 | Type: blockType, | 339 | Type: blockType, |
275 | Labels: labels, | 340 | Labels: labels, |
276 | Body: nil, | 341 | Body: &Body{ |
342 | SrcRange: ident.Range, | ||
343 | EndRange: ident.Range, | ||
344 | }, | ||
277 | 345 | ||
278 | TypeRange: ident.Range, | 346 | TypeRange: ident.Range, |
279 | LabelRanges: labelRanges, | 347 | LabelRanges: labelRanges, |
@@ -285,7 +353,51 @@ Token: | |||
285 | 353 | ||
286 | // Once we fall out here, the peeker is pointed just after our opening | 354 | // Once we fall out here, the peeker is pointed just after our opening |
287 | // brace, so we can begin our nested body parsing. | 355 | // brace, so we can begin our nested body parsing. |
288 | body, bodyDiags := p.ParseBody(TokenCBrace) | 356 | var body *Body |
357 | var bodyDiags hcl.Diagnostics | ||
358 | switch p.Peek().Type { | ||
359 | case TokenNewline, TokenEOF, TokenCBrace: | ||
360 | body, bodyDiags = p.ParseBody(TokenCBrace) | ||
361 | default: | ||
362 | // Special one-line, single-attribute block parsing mode. | ||
363 | body, bodyDiags = p.parseSingleAttrBody(TokenCBrace) | ||
364 | switch p.Peek().Type { | ||
365 | case TokenCBrace: | ||
366 | p.Read() // the happy path - just consume the closing brace | ||
367 | case TokenComma: | ||
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(), | ||
375 | }) | ||
376 | p.recover(TokenCBrace) | ||
377 | case TokenNewline: | ||
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(), | ||
384 | }) | ||
385 | p.recover(TokenCBrace) | ||
386 | default: | ||
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 | ||
389 | // recovery mode. | ||
390 | if !p.recovery { | ||
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(), | ||
396 | }) | ||
397 | } | ||
398 | p.recover(TokenCBrace) | ||
399 | } | ||
400 | } | ||
289 | diags = append(diags, bodyDiags...) | 401 | diags = append(diags, bodyDiags...) |
290 | cBraceRange := p.PrevRange() | 402 | cBraceRange := p.PrevRange() |
291 | 403 | ||
@@ -305,6 +417,17 @@ Token: | |||
305 | p.recoverAfterBodyItem() | 417 | p.recoverAfterBodyItem() |
306 | } | 418 | } |
307 | 419 | ||
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() { | ||
425 | body = &Body{ | ||
426 | SrcRange: hcl.RangeBetween(oBrace.Range, cBraceRange), | ||
427 | EndRange: cBraceRange, | ||
428 | } | ||
429 | } | ||
430 | |||
308 | return &Block{ | 431 | return &Block{ |
309 | Type: blockType, | 432 | Type: blockType, |
310 | Labels: labels, | 433 | Labels: labels, |
@@ -459,7 +582,14 @@ func (p *parser) parseBinaryOps(ops []map[TokenType]*Operation) (Expression, hcl | |||
459 | 582 | ||
460 | func (p *parser) parseExpressionWithTraversals() (Expression, hcl.Diagnostics) { | 583 | func (p *parser) parseExpressionWithTraversals() (Expression, hcl.Diagnostics) { |
461 | term, diags := p.parseExpressionTerm() | 584 | term, diags := p.parseExpressionTerm() |
462 | ret := term | 585 | ret, moreDiags := p.parseExpressionTraversals(term) |
586 | diags = append(diags, moreDiags...) | ||
587 | return ret, diags | ||
588 | } | ||
589 | |||
590 | func (p *parser) parseExpressionTraversals(from Expression) (Expression, hcl.Diagnostics) { | ||
591 | var diags hcl.Diagnostics | ||
592 | ret := from | ||
463 | 593 | ||
464 | Traversal: | 594 | Traversal: |
465 | for { | 595 | for { |
@@ -657,44 +787,81 @@ Traversal: | |||
657 | // the key value is something constant. | 787 | // the key value is something constant. |
658 | 788 | ||
659 | open := p.Read() | 789 | open := p.Read() |
660 | // TODO: If we have a TokenStar inside our brackets, parse as | 790 | switch p.Peek().Type { |
661 | // a Splat expression: foo[*].baz[0]. | 791 | case TokenStar: |
662 | var close Token | 792 | // This is a full splat expression, like foo[*], which consumes |
663 | p.PushIncludeNewlines(false) // arbitrary newlines allowed in brackets | 793 | // the rest of the traversal steps after it using a recursive |
664 | keyExpr, keyDiags := p.ParseExpression() | 794 | // call to this function. |
665 | diags = append(diags, keyDiags...) | 795 | p.Read() // consume star |
666 | if p.recovery && keyDiags.HasErrors() { | 796 | close := p.Read() |
667 | close = p.recover(TokenCBrack) | ||
668 | } else { | ||
669 | close = p.Read() | ||
670 | if close.Type != TokenCBrack && !p.recovery { | 797 | if close.Type != TokenCBrack && !p.recovery { |
671 | diags = append(diags, &hcl.Diagnostic{ | 798 | diags = append(diags, &hcl.Diagnostic{ |
672 | Severity: hcl.DiagError, | 799 | Severity: hcl.DiagError, |
673 | Summary: "Missing close bracket on index", | 800 | Summary: "Missing close bracket on splat index", |
674 | Detail: "The index operator must end with a closing bracket (\"]\").", | 801 | Detail: "The star for a full splat operator must be immediately followed by a closing bracket (\"]\").", |
675 | Subject: &close.Range, | 802 | Subject: &close.Range, |
676 | }) | 803 | }) |
677 | close = p.recover(TokenCBrack) | 804 | close = p.recover(TokenCBrack) |
678 | } | 805 | } |
679 | } | 806 | // Splat expressions use a special "anonymous symbol" as a |
680 | p.PopIncludeNewlines() | 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), | ||
811 | } | ||
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...) | ||
681 | 816 | ||
682 | if lit, isLit := keyExpr.(*LiteralValueExpr); isLit { | 817 | ret = &SplatExpr{ |
683 | litKey, _ := lit.Value(nil) | 818 | Source: ret, |
684 | rng := hcl.RangeBetween(open.Range, close.Range) | 819 | Each: travExpr, |
685 | step := hcl.TraverseIndex{ | 820 | Item: itemExpr, |
686 | Key: litKey, | 821 | |
687 | SrcRange: rng, | 822 | SrcRange: hcl.RangeBetween(open.Range, travExpr.Range()), |
823 | MarkerRange: hcl.RangeBetween(open.Range, close.Range), | ||
688 | } | 824 | } |
689 | ret = makeRelativeTraversal(ret, step, rng) | ||
690 | } else { | ||
691 | rng := hcl.RangeBetween(open.Range, close.Range) | ||
692 | ret = &IndexExpr{ | ||
693 | Collection: ret, | ||
694 | Key: keyExpr, | ||
695 | 825 | ||
696 | SrcRange: rng, | 826 | default: |
697 | OpenRange: open.Range, | 827 | |
828 | var close Token | ||
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) | ||
834 | } else { | ||
835 | close = p.Read() | ||
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, | ||
842 | }) | ||
843 | close = p.recover(TokenCBrack) | ||
844 | } | ||
845 | } | ||
846 | p.PopIncludeNewlines() | ||
847 | |||
848 | if lit, isLit := keyExpr.(*LiteralValueExpr); isLit { | ||
849 | litKey, _ := lit.Value(nil) | ||
850 | rng := hcl.RangeBetween(open.Range, close.Range) | ||
851 | step := hcl.TraverseIndex{ | ||
852 | Key: litKey, | ||
853 | SrcRange: rng, | ||
854 | } | ||
855 | ret = makeRelativeTraversal(ret, step, rng) | ||
856 | } else { | ||
857 | rng := hcl.RangeBetween(open.Range, close.Range) | ||
858 | ret = &IndexExpr{ | ||
859 | Collection: ret, | ||
860 | Key: keyExpr, | ||
861 | |||
862 | SrcRange: rng, | ||
863 | OpenRange: open.Range, | ||
864 | } | ||
698 | } | 865 | } |
699 | } | 866 | } |
700 | 867 | ||
@@ -813,7 +980,7 @@ func (p *parser) parseExpressionTerm() (Expression, hcl.Diagnostics) { | |||
813 | case TokenOQuote, TokenOHeredoc: | 980 | case TokenOQuote, TokenOHeredoc: |
814 | open := p.Read() // eat opening marker | 981 | open := p.Read() // eat opening marker |
815 | closer := p.oppositeBracket(open.Type) | 982 | closer := p.oppositeBracket(open.Type) |
816 | exprs, passthru, _, diags := p.parseTemplateInner(closer) | 983 | exprs, passthru, _, diags := p.parseTemplateInner(closer, tokenOpensFlushHeredoc(open)) |
817 | 984 | ||
818 | closeRange := p.PrevRange() | 985 | closeRange := p.PrevRange() |
819 | 986 | ||
@@ -891,11 +1058,10 @@ func (p *parser) parseExpressionTerm() (Expression, hcl.Diagnostics) { | |||
891 | } | 1058 | } |
892 | 1059 | ||
893 | func (p *parser) numberLitValue(tok Token) (cty.Value, hcl.Diagnostics) { | 1060 | func (p *parser) numberLitValue(tok Token) (cty.Value, hcl.Diagnostics) { |
894 | // We'll lean on the cty converter to do the conversion, to ensure that | 1061 | // The cty.ParseNumberVal is always the same behavior as converting a |
895 | // the behavior is the same as what would happen if converting a | 1062 | // string to a number, ensuring we always interpret decimal numbers in |
896 | // non-literal string to a number. | 1063 | // the same way. |
897 | numStrVal := cty.StringVal(string(tok.Bytes)) | 1064 | numVal, err := cty.ParseNumberVal(string(tok.Bytes)) |
898 | numVal, err := convert.Convert(numStrVal, cty.Number) | ||
899 | if err != nil { | 1065 | if err != nil { |
900 | ret := cty.UnknownVal(cty.Number) | 1066 | ret := cty.UnknownVal(cty.Number) |
901 | return ret, hcl.Diagnostics{ | 1067 | return ret, hcl.Diagnostics{ |
@@ -1087,13 +1253,19 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { | |||
1087 | panic("parseObjectCons called without peeker pointing to open brace") | 1253 | panic("parseObjectCons called without peeker pointing to open brace") |
1088 | } | 1254 | } |
1089 | 1255 | ||
1090 | p.PushIncludeNewlines(true) | 1256 | // We must temporarily stop looking at newlines here while we check for |
1091 | defer p.PopIncludeNewlines() | 1257 | // a "for" keyword, since for expressions are _not_ newline-sensitive, |
1092 | 1258 | // even though object constructors are. | |
1093 | if forKeyword.TokenMatches(p.Peek()) { | 1259 | p.PushIncludeNewlines(false) |
1260 | isFor := forKeyword.TokenMatches(p.Peek()) | ||
1261 | p.PopIncludeNewlines() | ||
1262 | if isFor { | ||
1094 | return p.finishParsingForExpr(open) | 1263 | return p.finishParsingForExpr(open) |
1095 | } | 1264 | } |
1096 | 1265 | ||
1266 | p.PushIncludeNewlines(true) | ||
1267 | defer p.PopIncludeNewlines() | ||
1268 | |||
1097 | var close Token | 1269 | var close Token |
1098 | 1270 | ||
1099 | var diags hcl.Diagnostics | 1271 | var diags hcl.Diagnostics |
@@ -1132,19 +1304,36 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { | |||
1132 | next = p.Peek() | 1304 | next = p.Peek() |
1133 | if next.Type != TokenEqual && next.Type != TokenColon { | 1305 | if next.Type != TokenEqual && next.Type != TokenColon { |
1134 | if !p.recovery { | 1306 | if !p.recovery { |
1135 | if next.Type == TokenNewline || next.Type == TokenComma { | 1307 | switch next.Type { |
1308 | case TokenNewline, TokenComma: | ||
1136 | diags = append(diags, &hcl.Diagnostic{ | 1309 | diags = append(diags, &hcl.Diagnostic{ |
1137 | Severity: hcl.DiagError, | 1310 | Severity: hcl.DiagError, |
1138 | Summary: "Missing item value", | 1311 | Summary: "Missing attribute value", |
1139 | Detail: "Expected an item value, introduced by an equals sign (\"=\").", | 1312 | Detail: "Expected an attribute value, introduced by an equals sign (\"=\").", |
1140 | Subject: &next.Range, | 1313 | Subject: &next.Range, |
1141 | Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), | 1314 | Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), |
1142 | }) | 1315 | }) |
1143 | } else { | 1316 | case TokenIdent: |
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. | ||
1144 | diags = append(diags, &hcl.Diagnostic{ | 1325 | diags = append(diags, &hcl.Diagnostic{ |
1145 | Severity: hcl.DiagError, | 1326 | Severity: hcl.DiagError, |
1146 | Summary: "Missing key/value separator", | 1327 | Summary: "Missing key/value separator", |
1147 | Detail: "Expected an equals sign (\"=\") to mark the beginning of the item value.", | 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(), | ||
1331 | }) | ||
1332 | default: | ||
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.", | ||
1148 | Subject: &next.Range, | 1337 | Subject: &next.Range, |
1149 | Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), | 1338 | Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), |
1150 | }) | 1339 | }) |
@@ -1182,8 +1371,8 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { | |||
1182 | if !p.recovery { | 1371 | if !p.recovery { |
1183 | diags = append(diags, &hcl.Diagnostic{ | 1372 | diags = append(diags, &hcl.Diagnostic{ |
1184 | Severity: hcl.DiagError, | 1373 | Severity: hcl.DiagError, |
1185 | Summary: "Missing item separator", | 1374 | Summary: "Missing attribute separator", |
1186 | Detail: "Expected a newline or comma to mark the beginning of the next item.", | 1375 | Detail: "Expected a newline or comma to mark the beginning of the next attribute.", |
1187 | Subject: &next.Range, | 1376 | Subject: &next.Range, |
1188 | Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), | 1377 | Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), |
1189 | }) | 1378 | }) |
@@ -1205,6 +1394,8 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { | |||
1205 | } | 1394 | } |
1206 | 1395 | ||
1207 | func (p *parser) finishParsingForExpr(open Token) (Expression, hcl.Diagnostics) { | 1396 | func (p *parser) finishParsingForExpr(open Token) (Expression, hcl.Diagnostics) { |
1397 | p.PushIncludeNewlines(false) | ||
1398 | defer p.PopIncludeNewlines() | ||
1208 | introducer := p.Read() | 1399 | introducer := p.Read() |
1209 | if !forKeyword.TokenMatches(introducer) { | 1400 | if !forKeyword.TokenMatches(introducer) { |
1210 | // Should never happen if callers are behaving | 1401 | // Should never happen if callers are behaving |
@@ -1277,7 +1468,7 @@ func (p *parser) finishParsingForExpr(open Token) (Expression, hcl.Diagnostics) | |||
1277 | diags = append(diags, &hcl.Diagnostic{ | 1468 | diags = append(diags, &hcl.Diagnostic{ |
1278 | Severity: hcl.DiagError, | 1469 | Severity: hcl.DiagError, |
1279 | Summary: "Invalid 'for' expression", | 1470 | Summary: "Invalid 'for' expression", |
1280 | Detail: "For expression requires 'in' keyword after names.", | 1471 | Detail: "For expression requires the 'in' keyword after its name declarations.", |
1281 | Subject: p.Peek().Range.Ptr(), | 1472 | Subject: p.Peek().Range.Ptr(), |
1282 | Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(), | 1473 | Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(), |
1283 | }) | 1474 | }) |
@@ -1305,7 +1496,7 @@ func (p *parser) finishParsingForExpr(open Token) (Expression, hcl.Diagnostics) | |||
1305 | diags = append(diags, &hcl.Diagnostic{ | 1496 | diags = append(diags, &hcl.Diagnostic{ |
1306 | Severity: hcl.DiagError, | 1497 | Severity: hcl.DiagError, |
1307 | Summary: "Invalid 'for' expression", | 1498 | Summary: "Invalid 'for' expression", |
1308 | Detail: "For expression requires colon after collection expression.", | 1499 | Detail: "For expression requires a colon after the collection expression.", |
1309 | Subject: p.Peek().Range.Ptr(), | 1500 | Subject: p.Peek().Range.Ptr(), |
1310 | Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(), | 1501 | Context: hcl.RangeBetween(open.Range, p.Peek().Range).Ptr(), |
1311 | }) | 1502 | }) |
@@ -1459,7 +1650,7 @@ Token: | |||
1459 | case TokenTemplateControl, TokenTemplateInterp: | 1650 | case TokenTemplateControl, TokenTemplateInterp: |
1460 | which := "$" | 1651 | which := "$" |
1461 | if tok.Type == TokenTemplateControl { | 1652 | if tok.Type == TokenTemplateControl { |
1462 | which = "!" | 1653 | which = "%" |
1463 | } | 1654 | } |
1464 | 1655 | ||
1465 | diags = append(diags, &hcl.Diagnostic{ | 1656 | diags = append(diags, &hcl.Diagnostic{ |
@@ -1472,7 +1663,16 @@ Token: | |||
1472 | Subject: &tok.Range, | 1663 | Subject: &tok.Range, |
1473 | Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(), | 1664 | Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(), |
1474 | }) | 1665 | }) |
1475 | p.recover(TokenTemplateSeqEnd) | 1666 | |
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("{ ... }") | ||
1674 | |||
1675 | p.recover(TokenTemplateSeqEnd) // we'll try to keep parsing after the sequence ends | ||
1476 | 1676 | ||
1477 | case TokenEOF: | 1677 | case TokenEOF: |
1478 | diags = append(diags, &hcl.Diagnostic{ | 1678 | diags = append(diags, &hcl.Diagnostic{ |
@@ -1493,7 +1693,7 @@ Token: | |||
1493 | Subject: &tok.Range, | 1693 | Subject: &tok.Range, |
1494 | Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(), | 1694 | Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(), |
1495 | }) | 1695 | }) |
1496 | p.recover(TokenOQuote) | 1696 | p.recover(TokenCQuote) |
1497 | break Token | 1697 | break Token |
1498 | 1698 | ||
1499 | } | 1699 | } |