From 0598a0b79b46d7106262d85a4fe5da01e389969d Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 17 Jul 2017 17:29:06 -0700 Subject: [PATCH] zcldec: initial implementation of BlockListSpec This initial pass supports only blocks without labels. BlockLabelSpec will be wired in here later to allow decoding blocks with labels too. --- zcldec/public_test.go | 52 ++++++++++++++++++++++++++++ zcldec/spec.go | 74 +++++++++++++++++++++++++++++++++++++++- zcldec/variables_test.go | 39 +++++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) diff --git a/zcldec/public_test.go b/zcldec/public_test.go index 3a545c2..d20c46d 100644 --- a/zcldec/public_test.go +++ b/zcldec/public_test.go @@ -146,6 +146,58 @@ b {} cty.EmptyObjectVal, 1, // only one "b" block is allowed }, + { + ` +b {} +b {} +`, + &BlockListSpec{ + TypeName: "b", + Nested: ObjectSpec{}, + }, + nil, + cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), + 0, + }, + { + ``, + &BlockListSpec{ + TypeName: "b", + Nested: ObjectSpec{}, + }, + nil, + cty.ListValEmpty(cty.DynamicPseudoType), + 0, + }, + { + ` +b {} +b {} +b {} +`, + &BlockListSpec{ + TypeName: "b", + Nested: ObjectSpec{}, + MaxItems: 2, + }, + nil, + cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal, cty.EmptyObjectVal}), + 1, // too many b blocks + }, + { + ` +b {} +b {} +`, + &BlockListSpec{ + TypeName: "b", + Nested: ObjectSpec{}, + MinItems: 10, + }, + nil, + cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), + 1, // insufficient b blocks + }, } for i, test := range tests { diff --git a/zcldec/spec.go b/zcldec/spec.go index 9a287e9..a3593ff 100644 --- a/zcldec/spec.go +++ b/zcldec/spec.go @@ -360,8 +360,80 @@ func (s *BlockListSpec) visitSameBodyChildren(cb visitFunc) { // leaf node ("Nested" does not use the same body) } +// blockSpec implementation +func (s *BlockListSpec) blockHeaderSchemata() []zcl.BlockHeaderSchema { + return []zcl.BlockHeaderSchema{ + { + Type: s.TypeName, + // FIXME: Need to peek into s.Nested to see if it has any + // BlockLabelSpec instances, which will define then how many + // labels we need. + }, + } +} + +// specNeedingVariables implementation +func (s *BlockListSpec) variablesNeeded(content *zcl.BodyContent) []zcl.Traversal { + var ret []zcl.Traversal + + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + ret = append(ret, Variables(childBlock.Body, s.Nested)...) + } + + return ret +} + func (s *BlockListSpec) decode(content *zcl.BodyContent, block *zcl.Block, ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) { - panic("BlockListSpec.decode not yet implemented") + var diags zcl.Diagnostics + + if s.Nested == nil { + panic("BlockSpec with no Nested Spec") + } + + var elems []cty.Value + var sourceRanges []zcl.Range + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false) + diags = append(diags, childDiags...) + elems = append(elems, val) + sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, childBlock, s.Nested)) + } + + if len(elems) < s.MinItems { + diags = append(diags, &zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: fmt.Sprintf("insufficient %s blocks", s.TypeName), + Detail: fmt.Sprintf("at least %d blocks are required", s.MinItems), + Subject: &content.MissingItemRange, + }) + } else if s.MaxItems > 0 && len(elems) > s.MaxItems { + diags = append(diags, &zcl.Diagnostic{ + Severity: zcl.DiagError, + Summary: fmt.Sprintf("too many %s blocks", s.TypeName), + Detail: fmt.Sprintf("no more than %d blocks are allowed", s.MaxItems), + Subject: &sourceRanges[s.MaxItems], + }) + } + + var ret cty.Value + + if len(elems) == 0 { + // FIXME: We don't currently have enough info to construct a type for + // an empty list, so we'll just stub it out. + ret = cty.ListValEmpty(cty.DynamicPseudoType) + } else { + ret = cty.ListVal(elems) + } + + return ret, diags } func (s *BlockListSpec) sourceRange(content *zcl.BodyContent, block *zcl.Block) zcl.Range { diff --git a/zcldec/variables_test.go b/zcldec/variables_test.go index dc5ed2e..da8eaee 100644 --- a/zcldec/variables_test.go +++ b/zcldec/variables_test.go @@ -85,6 +85,45 @@ b { }, }, }, + { + ` +b { + a = foo +} +b { + a = bar +} +c { + a = baz +} +`, + &BlockListSpec{ + TypeName: "b", + Nested: &AttrSpec{ + Name: "a", + }, + }, + []zcl.Traversal{ + { + zcl.TraverseRoot{ + Name: "foo", + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 3, Column: 7, Byte: 11}, + End: zcl.Pos{Line: 3, Column: 10, Byte: 14}, + }, + }, + }, + { + zcl.TraverseRoot{ + Name: "bar", + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 6, Column: 7, Byte: 27}, + End: zcl.Pos{Line: 6, Column: 10, Byte: 30}, + }, + }, + }, + }, + }, } for i, test := range tests {