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