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,8 +667,44 @@ 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:
|
||||||
|
// 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 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)
|
||||||
|
}
|
||||||
|
// 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),
|
||||||
|
}
|
||||||
|
// 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...)
|
||||||
|
|
||||||
|
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
|
var close Token
|
||||||
p.PushIncludeNewlines(false) // arbitrary newlines allowed in brackets
|
p.PushIncludeNewlines(false) // arbitrary newlines allowed in brackets
|
||||||
keyExpr, keyDiags := p.ParseExpression()
|
keyExpr, keyDiags := p.ParseExpression()
|
||||||
@ -700,6 +743,7 @@ Traversal:
|
|||||||
OpenRange: open.Range,
|
OpenRange: open.Range,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break Traversal
|
break Traversal
|
||||||
|
Loading…
x
Reference in New Issue
Block a user