hcl: UnwrapExpression and UnwrapExpressionUntil
A pattern has emerged of wrapping Expression instances with other Expressions in order to subtly modify their behavior. A key example of this is in ext/dynblock, where wrap an expression in order to introduce our additional iteration variable for expressions in dynamic blocks. Rather than having each wrapper expression implement wrapping implementations for our various syntax-level-analysis functions (like ExprList and AbsTraversalForExpr), instead we define a standard mechanism to unwrap expressions back to the lowest-level object -- usually an AST node -- and then use this in all of our analyses that look at the expression's structure rather than its value.
This commit is contained in:
parent
60b539d5d7
commit
34e27c038a
@ -35,26 +35,8 @@ func (e exprWrap) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return e.Expression.Value(extCtx)
|
||||
}
|
||||
|
||||
// Passthrough implementation for hcl.ExprList
|
||||
func (e exprWrap) ExprList() []hcl.Expression {
|
||||
type exprList interface {
|
||||
ExprList() []hcl.Expression
|
||||
}
|
||||
|
||||
if el, supported := e.Expression.(exprList); supported {
|
||||
return el.ExprList()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Passthrough implementation for hcl.AbsTraversalForExpr and hcl.RelTraversalForExpr
|
||||
func (e exprWrap) AsTraversal() hcl.Traversal {
|
||||
type asTraversal interface {
|
||||
AsTraversal() hcl.Traversal
|
||||
}
|
||||
|
||||
if at, supported := e.Expression.(asTraversal); supported {
|
||||
return at.AsTraversal()
|
||||
}
|
||||
return nil
|
||||
// UnwrapExpression returns the expression being wrapped by this instance.
|
||||
// This allows the original expression to be recovered by hcl.UnwrapExpression.
|
||||
func (e exprWrap) UnwrapExpression() hcl.Expression {
|
||||
return e.Expression
|
||||
}
|
||||
|
@ -8,13 +8,20 @@ package hcl
|
||||
// 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.
|
||||
// be extracted. Alternatively, an implementation can support
|
||||
// UnwrapExpression to delegate handling of this function to a wrapped
|
||||
// Expression object.
|
||||
func ExprList(expr Expression) ([]Expression, Diagnostics) {
|
||||
type exprList interface {
|
||||
ExprList() []Expression
|
||||
}
|
||||
|
||||
if exL, supported := expr.(exprList); supported {
|
||||
physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool {
|
||||
_, supported := expr.(exprList)
|
||||
return supported
|
||||
})
|
||||
|
||||
if exL, supported := physExpr.(exprList); supported {
|
||||
if list := exL.ExprList(); list != nil {
|
||||
return list, nil
|
||||
}
|
||||
|
68
hcl/expr_unwrap.go
Normal file
68
hcl/expr_unwrap.go
Normal file
@ -0,0 +1,68 @@
|
||||
package hcl
|
||||
|
||||
type unwrapExpression interface {
|
||||
UnwrapExpression() Expression
|
||||
}
|
||||
|
||||
// UnwrapExpression removes any "wrapper" expressions from the given expression,
|
||||
// to recover the representation of the physical expression given in source
|
||||
// code.
|
||||
//
|
||||
// Sometimes wrapping expressions are used to modify expression behavior, e.g.
|
||||
// in extensions that need to make some local variables available to certain
|
||||
// sub-trees of the configuration. This can make it difficult to reliably
|
||||
// type-assert on the physical AST types used by the underlying syntax.
|
||||
//
|
||||
// Unwrapping an expression may modify its behavior by stripping away any
|
||||
// additional constraints or capabilities being applied to the Value and
|
||||
// Variables methods, so this function should generally only be used prior
|
||||
// to operations that concern themselves with the static syntax of the input
|
||||
// configuration, and not with the effective value of the expression.
|
||||
//
|
||||
// Wrapper expression types must support unwrapping by implementing a method
|
||||
// called UnwrapExpression that takes no arguments and returns the embedded
|
||||
// Expression. Implementations of this method should peel away only one level
|
||||
// of wrapping, if multiple are present. This method may return nil to
|
||||
// indicate _dynamically_ that no wrapped expression is available, for
|
||||
// expression types that might only behave as wrappers in certain cases.
|
||||
func UnwrapExpression(expr Expression) Expression {
|
||||
for {
|
||||
unwrap, wrapped := expr.(unwrapExpression)
|
||||
if !wrapped {
|
||||
return expr
|
||||
}
|
||||
innerExpr := unwrap.UnwrapExpression()
|
||||
if innerExpr == nil {
|
||||
return expr
|
||||
}
|
||||
expr = innerExpr
|
||||
}
|
||||
}
|
||||
|
||||
// UnwrapExpressionUntil is similar to UnwrapExpression except it gives the
|
||||
// caller an opportunity to test each level of unwrapping to see each a
|
||||
// particular expression is accepted.
|
||||
//
|
||||
// This could be used, for example, to unwrap until a particular other
|
||||
// interface is satisfied, regardless of wrap wrapping level it is satisfied
|
||||
// at.
|
||||
//
|
||||
// The given callback function must return false to continue wrapping, or
|
||||
// true to accept and return the proposed expression given. If the callback
|
||||
// function rejects even the final, physical expression then the result of
|
||||
// this function is nil.
|
||||
func UnwrapExpressionUntil(expr Expression, until func(Expression) bool) Expression {
|
||||
for {
|
||||
if until(expr) {
|
||||
return expr
|
||||
}
|
||||
unwrap, wrapped := expr.(unwrapExpression)
|
||||
if !wrapped {
|
||||
return nil
|
||||
}
|
||||
expr = unwrap.UnwrapExpression()
|
||||
if expr == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,9 @@ package hcl
|
||||
// A particular Expression implementation can support this function by
|
||||
// offering a method called AsTraversal that takes no arguments and
|
||||
// returns either a valid absolute traversal or nil to indicate that
|
||||
// no traversal is possible.
|
||||
// no traversal is possible. Alternatively, an implementation can support
|
||||
// UnwrapExpression to delegate handling of this function to a wrapped
|
||||
// Expression object.
|
||||
//
|
||||
// In most cases the calling application is interested in the value
|
||||
// that results from an expression, but in rarer cases the application
|
||||
@ -20,7 +22,12 @@ func AbsTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
|
||||
AsTraversal() Traversal
|
||||
}
|
||||
|
||||
if asT, supported := expr.(asTraversal); supported {
|
||||
physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool {
|
||||
_, supported := expr.(asTraversal)
|
||||
return supported
|
||||
})
|
||||
|
||||
if asT, supported := physExpr.(asTraversal); supported {
|
||||
if traversal := asT.AsTraversal(); traversal != nil {
|
||||
return traversal, nil
|
||||
}
|
||||
|
@ -17,6 +17,11 @@ type asTraversalDeclined struct {
|
||||
staticExpr
|
||||
}
|
||||
|
||||
type asTraversalWrappedDelegated struct {
|
||||
original Expression
|
||||
staticExpr
|
||||
}
|
||||
|
||||
func (e asTraversalSupported) AsTraversal() Traversal {
|
||||
return Traversal{
|
||||
TraverseRoot{
|
||||
@ -29,6 +34,10 @@ func (e asTraversalDeclined) AsTraversal() Traversal {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e asTraversalWrappedDelegated) UnwrapExpression() Expression {
|
||||
return e.original
|
||||
}
|
||||
|
||||
func TestAbsTraversalForExpr(t *testing.T) {
|
||||
tests := []struct {
|
||||
Expr Expression
|
||||
@ -46,6 +55,20 @@ func TestAbsTraversalForExpr(t *testing.T) {
|
||||
asTraversalDeclined{},
|
||||
"",
|
||||
},
|
||||
{
|
||||
asTraversalWrappedDelegated{
|
||||
original: asTraversalSupported{RootName: "foo"},
|
||||
},
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
asTraversalWrappedDelegated{
|
||||
original: asTraversalWrappedDelegated{
|
||||
original: asTraversalSupported{RootName: "foo"},
|
||||
},
|
||||
},
|
||||
"foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
Loading…
Reference in New Issue
Block a user