hcl: ExprList function

This helper allows a calling application to require a given expression be
some sort of list constructor (tuple constructor in native syntax, or
array in JSON) and peel off that outer level of list to obtain a slice
of the Expression objects inside.

This is useful in rare cases where the calling application needs to
extract the expressions within the list without evaluating the entire list
expression first. For example, the expressions that result from this
function might be passed into AbsTraversalForExpr in situations where the
caller requires a static list of static traversals that will never
actually be evaluated.
This commit is contained in:
Martin Atkins 2018-01-12 23:30:41 -08:00
parent 0949d55133
commit 77c855c5ed
5 changed files with 90 additions and 0 deletions

30
hcl/expr_list.go Normal file
View File

@ -0,0 +1,30 @@
package hcl
// ExprList tests if the given expression is a static list construct and,
// if so, extracts the expressions that represent the list elements.
// If the given expression is not a static list, error diagnostics are
// returned.
//
// A particular Expression implementation can support this function by
// offering a method called ExprList that takes no arguments and returns
// []Expression. This method should return nil if a static list cannot
// be extracted.
func ExprList(expr Expression) ([]Expression, Diagnostics) {
type exprList interface {
ExprList() []Expression
}
if exL, supported := expr.(exprList); supported {
if list := exL.ExprList(); list != nil {
return list, nil
}
}
return nil, Diagnostics{
&Diagnostic{
Severity: DiagError,
Summary: "Invalid expression",
Detail: "A static list expression is required.",
Subject: expr.StartRange().Ptr(),
},
}
}

View File

@ -544,6 +544,15 @@ func (e *TupleConsExpr) StartRange() hcl.Range {
return e.OpenRange
}
// Implementation for hcl.ExprList
func (e *TupleConsExpr) ExprList() []hcl.Expression {
ret := make([]hcl.Expression, len(e.Exprs))
for i, expr := range e.Exprs {
ret[i] = expr
}
return ret
}
type ObjectConsExpr struct {
Items []ObjectConsItem

View File

@ -1101,3 +1101,21 @@ func TestExpressionAsTraversal(t *testing.T) {
t.Fatalf("wrong root name %q; want %q", traversal.RootName(), "a")
}
}
func TestStaticExpressionList(t *testing.T) {
expr, _ := ParseExpression([]byte("[0, a, true]"), "", hcl.Pos{})
exprs, diags := hcl.ExprList(expr)
if len(diags) != 0 {
t.Fatalf("unexpected diagnostics")
}
if len(exprs) != 3 {
t.Fatalf("wrong result %#v; want length 3", exprs)
}
first, ok := exprs[0].(*LiteralValueExpr)
if !ok {
t.Fatalf("first expr has wrong type %T; want *zclsyntax.LiteralValueExpr", exprs[0])
}
if !first.Val.RawEquals(cty.Zero) {
t.Fatalf("wrong first value %#v; want cty.Zero", first.Val)
}
}

View File

@ -370,3 +370,17 @@ func (e *expression) AsTraversal() hcl.Traversal {
return nil
}
}
// Implementation for hcl.ExprList.
func (e *expression) ExprList() []hcl.Expression {
switch v := e.src.(type) {
case *arrayVal:
ret := make([]hcl.Expression, len(v.Values))
for i, node := range v.Values {
ret[i] = &expression{src: node}
}
return ret
default:
return nil
}
}

View File

@ -753,3 +753,22 @@ func TestExpressionAsTraversal(t *testing.T) {
t.Fatalf("incorrect traversal %#v; want length 3", traversal)
}
}
func TestStaticExpressionList(t *testing.T) {
e := &expression{
src: &arrayVal{
Values: []node{
&stringVal{
Value: "hello",
},
},
},
}
exprs := e.ExprList()
if len(exprs) != 1 {
t.Fatalf("incorrect exprs %#v; want length 1", exprs)
}
if exprs[0].(*expression).src != e.src.(*arrayVal).Values[0] {
t.Fatalf("wrong first expression node")
}
}