zclsyntax: allow numeric "attributes" inside attr-only splats

Terraform interprets HIL variables in such a way that it allows numeric
attribute names which then get interpreted as numeric indices into a
list. This is used to work around the fact that the splat expressions
don't work for the index operator.

zcl has "full splats" that _do_ support the index operator, but to allow
old Terraform configs to be processed by zcl we'll accept this special
case within attribute-only-splats only.

For the moment this is a special exception made by this specific
implementation of zcl rather than part of the spec, since it's
specifically a pragmatic Terraform migration strategy, but it might get
upgraded to full spec status later if we end up needing to support it
in other host languages.

This requires the scanner to be a little more picky about the ending
of numeric literals, so that they won't absorb the trailing period after
the number in foo.*.baz.1.baz . This is okay because the spec doesn't
allow trailing periods anyway, and this is not actually a change in
final behavior because the parser was already catching this situation
and rejecting it at a later point.
This commit is contained in:
Martin Atkins 2017-06-24 09:39:16 -07:00
parent 2c9302b699
commit 59a1343216
4 changed files with 2376 additions and 2338 deletions

View File

@ -700,6 +700,24 @@ upper(
}), }),
0, 0,
}, },
{
`[["hello"], ["world", "unused"]].*.0`,
nil,
cty.TupleVal([]cty.Value{
cty.StringVal("hello"),
cty.StringVal("world"),
}),
0,
},
{
`[[{name:"foo"}], [{name:"bar"}, {name:"baz"}]].*.0.name`,
nil,
cty.TupleVal([]cty.Value{
cty.StringVal("foo"),
cty.StringVal("bar"),
}),
0,
},
{ {
// For an "attribute-only" splat, an index operator applies to // For an "attribute-only" splat, an index operator applies to
// the splat result as a whole, rather than being incorporated // the splat result as a whole, rather than being incorporated

View File

@ -508,6 +508,24 @@ Traversal:
firstRange = p.NextRange() firstRange = p.NextRange()
for p.Peek().Type == TokenDot { for p.Peek().Type == TokenDot {
dot := p.Read() dot := p.Read()
if p.Peek().Type == TokenNumberLit {
// Continuing the "weird stuff inherited from HIL"
// theme, we also allow numbers as attribute names
// inside splats and interpret them as indexing
// into a list, for expressions like:
// foo.bar.*.baz.0.foo
numTok := p.Read()
numVal, numDiags := p.numberLitValue(numTok)
diags = append(diags, numDiags...)
trav = append(trav, zcl.TraverseIndex{
Key: numVal,
SrcRange: zcl.RangeBetween(dot.Range, numTok.Range),
})
lastRange = numTok.Range
continue
}
if p.Peek().Type != TokenIdent { if p.Peek().Type != TokenIdent {
if !p.recovery { if !p.recovery {
if p.Peek().Type == TokenStar { if p.Peek().Type == TokenStar {

File diff suppressed because it is too large Load Diff

View File

@ -33,7 +33,8 @@ func scanTokens(data []byte, filename string, start zcl.Pos, mode scanMode) []To
); );
BrokenUTF8 = any - AnyUTF8; BrokenUTF8 = any - AnyUTF8;
NumberLit = digit (digit|'.'|('e'|'E') ('+'|'-')? digit)*; NumberLitContinue = (digit|'.'|('e'|'E') ('+'|'-')? digit);
NumberLit = digit ("" | (NumberLitContinue - '.') | (NumberLitContinue* (NumberLitContinue - '.')));
Ident = ID_Start (ID_Continue | '-')*; Ident = ID_Start (ID_Continue | '-')*;
# Symbols that just represent themselves are handled as a single rule. # Symbols that just represent themselves are handled as a single rule.