hcl/hclsyntax: Parsing of the "full splat" operator

The evaluation of this was there but the parsing was still a TODO comment
from early development. Whoops!

Fortunately the existing parser functionality makes this straightforward
since we can just have the traversal parser recursively call itself.

This fixes #63.
This commit is contained in:
Martin Atkins 2018-12-12 17:41:28 -08:00
parent ebe27107e1
commit b056768a1c
2 changed files with 107 additions and 30 deletions

View File

@ -781,6 +781,14 @@ upper(
}),
0,
},
{
`{name: "Steve"}[*].name`,
nil,
cty.TupleVal([]cty.Value{
cty.StringVal("Steve"),
}),
0,
},
{
`set.*.name`,
&hcl.EvalContext{
@ -915,6 +923,31 @@ upper(
cty.StringVal("Steve"),
0,
},
{
// For a "full" splat, an index operator is consumed as part
// of the splat's traversal.
`[{names: ["Steve"]}, {names: ["Ermintrude"]}][*].names[0]`,
nil,
cty.TupleVal([]cty.Value{cty.StringVal("Steve"), cty.StringVal("Ermintrude")}),
0,
},
{
// Another "full" splat, this time with the index first.
`[[{name: "Steve"}], [{name: "Ermintrude"}]][*][0].name`,
nil,
cty.TupleVal([]cty.Value{cty.StringVal("Steve"), cty.StringVal("Ermintrude")}),
0,
},
{
// Full splats can nest, which produces nested tuples.
`[[{name: "Steve"}], [{name: "Ermintrude"}]][*][*].name`,
nil,
cty.TupleVal([]cty.Value{
cty.TupleVal([]cty.Value{cty.StringVal("Steve")}),
cty.TupleVal([]cty.Value{cty.StringVal("Ermintrude")}),
}),
0,
},
{
`[["hello"], ["goodbye"]].*.*`,
nil,

View File

@ -462,7 +462,14 @@ func (p *parser) parseBinaryOps(ops []map[TokenType]*Operation) (Expression, hcl
func (p *parser) parseExpressionWithTraversals() (Expression, hcl.Diagnostics) {
term, diags := p.parseExpressionTerm()
ret := term
ret, moreDiags := p.parseExpressionTraversals(term)
diags = append(diags, moreDiags...)
return ret, diags
}
func (p *parser) parseExpressionTraversals(from Expression) (Expression, hcl.Diagnostics) {
var diags hcl.Diagnostics
ret := from
Traversal:
for {
@ -660,44 +667,81 @@ Traversal:
// the key value is something constant.
open := p.Read()
// TODO: If we have a TokenStar inside our brackets, parse as
// a Splat expression: foo[*].baz[0].
var close Token
p.PushIncludeNewlines(false) // arbitrary newlines allowed in brackets
keyExpr, keyDiags := p.ParseExpression()
diags = append(diags, keyDiags...)
if p.recovery && keyDiags.HasErrors() {
close = p.recover(TokenCBrack)
} else {
close = p.Read()
switch p.Peek().Type {
case TokenStar:
// This is a full splat expression, like foo[*], which consumes
// the rest of the traversal steps after it using a recursive
// call to this function.
p.Read() // consume star
close := p.Read()
if close.Type != TokenCBrack && !p.recovery {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing close bracket on index",
Detail: "The index operator must end with a closing bracket (\"]\").",
Summary: "Missing close bracket on splat index",
Detail: "The star for a full splat operator must be immediately followed by a closing bracket (\"]\").",
Subject: &close.Range,
})
close = p.recover(TokenCBrack)
}
}
p.PopIncludeNewlines()
if lit, isLit := keyExpr.(*LiteralValueExpr); isLit {
litKey, _ := lit.Value(nil)
rng := hcl.RangeBetween(open.Range, close.Range)
step := hcl.TraverseIndex{
Key: litKey,
SrcRange: rng,
// Splat expressions use a special "anonymous symbol" as a
// placeholder in an expression to be evaluated once for each
// item in the source expression.
itemExpr := &AnonSymbolExpr{
SrcRange: hcl.RangeBetween(open.Range, close.Range),
}
ret = makeRelativeTraversal(ret, step, rng)
} else {
rng := hcl.RangeBetween(open.Range, close.Range)
ret = &IndexExpr{
Collection: ret,
Key: keyExpr,
// Now we'll recursively call this same function to eat any
// remaining traversal steps against the anonymous symbol.
travExpr, nestedDiags := p.parseExpressionTraversals(itemExpr)
diags = append(diags, nestedDiags...)
SrcRange: rng,
OpenRange: open.Range,
ret = &SplatExpr{
Source: ret,
Each: travExpr,
Item: itemExpr,
SrcRange: hcl.RangeBetween(open.Range, travExpr.Range()),
MarkerRange: hcl.RangeBetween(open.Range, close.Range),
}
default:
var close Token
p.PushIncludeNewlines(false) // arbitrary newlines allowed in brackets
keyExpr, keyDiags := p.ParseExpression()
diags = append(diags, keyDiags...)
if p.recovery && keyDiags.HasErrors() {
close = p.recover(TokenCBrack)
} else {
close = p.Read()
if close.Type != TokenCBrack && !p.recovery {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing close bracket on index",
Detail: "The index operator must end with a closing bracket (\"]\").",
Subject: &close.Range,
})
close = p.recover(TokenCBrack)
}
}
p.PopIncludeNewlines()
if lit, isLit := keyExpr.(*LiteralValueExpr); isLit {
litKey, _ := lit.Value(nil)
rng := hcl.RangeBetween(open.Range, close.Range)
step := hcl.TraverseIndex{
Key: litKey,
SrcRange: rng,
}
ret = makeRelativeTraversal(ret, step, rng)
} else {
rng := hcl.RangeBetween(open.Range, close.Range)
ret = &IndexExpr{
Collection: ret,
Key: keyExpr,
SrcRange: rng,
OpenRange: open.Range,
}
}
}