From 22bc5ce5c6634ad6c2a0ac7f1936a649cdd01cec Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 16 Jun 2017 08:33:35 -0700 Subject: [PATCH] zclsyntax: parsing of "attribute-only" splat expressions Attribute-only splat expressions, indicated by using the star symbol as if it were an attribute, are inherited from HIL and are a limited sort of splat that only works for walking through attributes in each item of its source. Later zcl will also get full splat expressions, whose syntax is using the star symbol as if it were an _index_, which will generalize to supporting _all_ postfix traversal operations, including indexing and nested splats. --- zcl/zclsyntax/expression_test.go | 44 ++++++++++++++++++++++++ zcl/zclsyntax/parser.go | 58 +++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/zcl/zclsyntax/expression_test.go b/zcl/zclsyntax/expression_test.go index 3dda1bc..335cde0 100644 --- a/zcl/zclsyntax/expression_test.go +++ b/zcl/zclsyntax/expression_test.go @@ -486,6 +486,50 @@ upper( 0, }, + { + `[{name: "Steve"}, {name: "Ermintrude"}].*.name`, + nil, + cty.TupleVal([]cty.Value{ + cty.StringVal("Steve"), + cty.StringVal("Ermintrude"), + }), + 0, + }, + { + `{name: "Steve"}.*.name`, + nil, + cty.TupleVal([]cty.Value{ + cty.StringVal("Steve"), + }), + 0, + }, + { + `["hello", "goodbye"].*`, + nil, + cty.TupleVal([]cty.Value{ + cty.StringVal("hello"), + cty.StringVal("goodbye"), + }), + 0, + }, + { + `"hello".*`, + nil, + cty.TupleVal([]cty.Value{ + cty.StringVal("hello"), + }), + 0, + }, + { + // For an "attribute-only" splat, an index operator applies to + // the splat result as a whole, rather than being incorporated + // into the splat traversal itself. + `[{name: "Steve"}, {name: "Ermintrude"}].*.name[0]`, + nil, + cty.StringVal("Steve"), + 0, + }, + { `["hello"][0]`, nil, diff --git a/zcl/zclsyntax/parser.go b/zcl/zclsyntax/parser.go index 22ce109..a082a8c 100644 --- a/zcl/zclsyntax/parser.go +++ b/zcl/zclsyntax/parser.go @@ -498,7 +498,61 @@ Traversal: ret = makeRelativeTraversal(ret, step, rng) - // TODO: TokenStar, for splats + case TokenStar: + // "Attribute-only" splat expression. + // (This is a kinda weird construct inherited from HIL, which + // behaves a bit like a [*] splat except that it is only able + // to do attribute traversals into each of its elements, + // whereas foo[*] can support _any_ traversal. + marker := p.Read() // eat star + trav := make(zcl.Traversal, 0, 1) + var firstRange, lastRange zcl.Range + firstRange = p.NextRange() + for p.Peek().Type == TokenDot { + dot := p.Read() + if p.Peek().Type != TokenIdent { + if !p.recovery { + diags = append(diags, &zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: "Invalid attribute name", + Detail: "An attribute name is required after a dot.", + Subject: &attrTok.Range, + }) + } + p.setRecovery() + continue Traversal + } + + attrTok := p.Read() + trav = append(trav, zcl.TraverseAttr{ + Name: string(attrTok.Bytes), + SrcRange: zcl.RangeBetween(dot.Range, attrTok.Range), + }) + lastRange = attrTok.Range + } + + itemExpr := &AnonSymbolExpr{ + SrcRange: zcl.RangeBetween(dot.Range, marker.Range), + } + var travExpr Expression + if len(trav) == 0 { + travExpr = itemExpr + } else { + travExpr = &RelativeTraversalExpr{ + Source: itemExpr, + Traversal: trav, + SrcRange: zcl.RangeBetween(firstRange, lastRange), + } + } + + ret = &SplatExpr{ + Source: ret, + Each: travExpr, + Item: itemExpr, + + SrcRange: zcl.RangeBetween(dot.Range, lastRange), + MarkerRange: zcl.RangeBetween(dot.Range, marker.Range), + } default: diags = append(diags, &zcl.Diagnostic{ @@ -523,6 +577,8 @@ 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()