From 1535d4b708e982348edb00e3fa1a744c767b0ee2 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 22 May 2017 18:54:23 -0700 Subject: [PATCH] Ability to look up the variables in an expression --- gozcl/decode_test.go | 4 ++++ zcl/hclhil/structure.go | 28 ++++++++++++++++++++++ zcl/hclhil/template.go | 53 +++++++++++++++++++++++++++++++++++++++++ zcl/json/structure.go | 39 ++++++++++++++++++++++++++++++ zcl/structure.go | 11 +++++---- 5 files changed, 131 insertions(+), 4 deletions(-) diff --git a/gozcl/decode_test.go b/gozcl/decode_test.go index 7c910f0..66e7b49 100644 --- a/gozcl/decode_test.go +++ b/gozcl/decode_test.go @@ -571,3 +571,7 @@ func (e *fixedExpression) Range() (r zcl.Range) { func (e *fixedExpression) StartRange() (r zcl.Range) { return } + +func (e *fixedExpression) Variables() []zcl.Traversal { + return nil +} diff --git a/zcl/hclhil/structure.go b/zcl/hclhil/structure.go index 500a5ea..d99c18d 100644 --- a/zcl/hclhil/structure.go +++ b/zcl/hclhil/structure.go @@ -310,6 +310,34 @@ func (e *expression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) { return ctyValueFromHCLNode(e.src, ctx) } +func (e *expression) Variables() []zcl.Traversal { + node := e.src + var vars []zcl.Traversal + + switch tn := node.(type) { + case *hclast.LiteralType: + tok := tn.Token + switch tok.Type { + case hcltoken.STRING, hcltoken.HEREDOC: + // TODO: HIL parsing and evaluation, if ctx is non-nil. + } + case *hclast.ObjectType: + list := tn.List + attrs, _ := (&body{oli: list}).JustAttributes() + if attrs != nil { + for _, attr := range attrs { + vars = append(vars, attr.Expr.Variables()...) + } + } + case *hclast.ListType: + nodes := tn.List + for _, node := range nodes { + vars = append(vars, (&expression{src: node}).Variables()...) + } + } + return vars +} + func (e *expression) Range() zcl.Range { return rangeFromHCLPos(e.src.Pos()) } diff --git a/zcl/hclhil/template.go b/zcl/hclhil/template.go index b036fbc..2ef66bf 100644 --- a/zcl/hclhil/template.go +++ b/zcl/hclhil/template.go @@ -3,6 +3,7 @@ package hclhil import ( "fmt" "strconv" + "strings" "github.com/apparentlymart/go-cty/cty" "github.com/apparentlymart/go-cty/cty/function" @@ -45,6 +46,58 @@ func (e *templateExpression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnos return ctyValueFromHILNode(e.node, cfg) } +func (e *templateExpression) Variables() []zcl.Traversal { + var vars []zcl.Traversal + e.node.Accept(func(n hilast.Node) hilast.Node { + vn, ok := n.(*hilast.VariableAccess) + if !ok { + return n + } + + rawName := vn.Name + parts := strings.Split(rawName, ".") + if len(parts) == 0 { + return n + } + + tr := make(zcl.Traversal, 0, len(parts)) + tr = append(tr, zcl.TraverseRoot{ + Name: parts[0], + SrcRange: rangeFromHILPos(n.Pos()), + }) + + for _, name := range parts { + if nv, err := strconv.Atoi(name); err == nil { + // Turn this into a sequence index in zcl land, to save + // callers from having to understand both HIL-style numeric + // attributes and zcl-style indices. + tr = append(tr, zcl.TraverseIndex{ + Key: cty.NumberIntVal(int64(nv)), + SrcRange: rangeFromHILPos(n.Pos()), + }) + continue + } + + if name == "*" { + // TODO: support splat traversals, but that requires some + // more work here because we need to then accumulate the + // rest of the parts into the splat's own "Each" traversal. + continue + } + + tr = append(tr, zcl.TraverseAttr{ + Name: name, + SrcRange: rangeFromHILPos(n.Pos()), + }) + } + + vars = append(vars, tr) + + return n + }) + return vars +} + func (e *templateExpression) Range() zcl.Range { return rangeFromHILPos(e.node.Pos()) } diff --git a/zcl/json/structure.go b/zcl/json/structure.go index ff55ad4..6fa8d5d 100644 --- a/zcl/json/structure.go +++ b/zcl/json/structure.go @@ -307,6 +307,45 @@ func (e *expression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) { } } +func (e *expression) Variables() []zcl.Traversal { + var vars []zcl.Traversal + + switch v := e.src.(type) { + case *stringVal: + if e.useHIL { + // Legacy interface to parse HCL-style JSON with HIL expressions. + templateSrc := v.Value + hilExpr, _ := hclhil.ParseTemplateEmbedded( + []byte(templateSrc), + v.SrcRange.Filename, + zcl.Pos{ + // skip over the opening quote mark + Byte: v.SrcRange.Start.Byte + 1, + Line: v.SrcRange.Start.Line, + Column: v.SrcRange.Start.Column, + }, + ) + if hilExpr != nil { + vars = append(vars, hilExpr.Variables()...) + } + } + + // FIXME: Once the native zcl template language parser is implemented, + // parse with that and look for variables in there too, + + case *arrayVal: + for _, jsonVal := range v.Values { + vars = append(vars, (&expression{src: jsonVal, useHIL: e.useHIL}).Variables()...) + } + case *objectVal: + for _, jsonAttr := range v.Attrs { + vars = append(vars, (&expression{src: jsonAttr.Value, useHIL: e.useHIL}).Variables()...) + } + } + + return vars +} + func (e *expression) Range() zcl.Range { return e.src.Range() } diff --git a/zcl/structure.go b/zcl/structure.go index b173674..154a1e2 100644 --- a/zcl/structure.go +++ b/zcl/structure.go @@ -110,12 +110,15 @@ type Expression interface { // the specific symbol in question. Value(ctx *EvalContext) (cty.Value, Diagnostics) + // Variables returns a list of variables referenced in the receiving + // expression. These are expressed as absolute Traversals, so may include + // additional information about how the variable is used, such as + // attribute lookups, which the calling application can potentially use + // to only selectively populate the scope. + Variables() []Traversal + Range() Range StartRange() Range - - // TODO: A "Variables" method that returns a description of all of the - // variables used in the expression, so callers can populate the scope - // only with variables that are actually used. } // OfType filters the receiving block sequence by block type name,