hcl: ExprMap function

This is similar to the ExprList function but for map-like constructs
(object constructors in the native syntax). It allows a more-advanced
calling application to analyze the physical structure of the configuration
directly, rather than analyzing the dynamic results of its expressions.

This is useful when creating what appear to be first-class language
constructs out of the language's grammar elements.

In the JSON syntax, a static map construct is expressed as a direct JSON
object. As with ExprList, this bypasses any dynamic expression evaluation
behavior and requires the user to provide a literal JSON object, though
the calling application is then free to evaluate the key/value expressions
inside in whatever way makes sense.
This commit is contained in:
Martin Atkins 2018-02-23 08:41:58 -08:00
parent 8c3aa9a6d4
commit 397fa07dea
3 changed files with 76 additions and 0 deletions

44
hcl/expr_map.go Normal file
View File

@ -0,0 +1,44 @@
package hcl
// ExprMap tests if the given expression is a static map construct and,
// if so, extracts the expressions that represent the map elements.
// If the given expression is not a static map, error diagnostics are
// returned.
//
// A particular Expression implementation can support this function by
// offering a method called ExprMap that takes no arguments and returns
// []KeyValuePair. This method should return nil if a static map cannot
// be extracted. Alternatively, an implementation can support
// UnwrapExpression to delegate handling of this function to a wrapped
// Expression object.
func ExprMap(expr Expression) ([]KeyValuePair, Diagnostics) {
type exprMap interface {
ExprMap() []KeyValuePair
}
physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool {
_, supported := expr.(exprMap)
return supported
})
if exM, supported := physExpr.(exprMap); supported {
if pairs := exM.ExprMap(); pairs != nil {
return pairs, nil
}
}
return nil, Diagnostics{
&Diagnostic{
Severity: DiagError,
Summary: "Invalid expression",
Detail: "A static map expression is required.",
Subject: expr.StartRange().Ptr(),
},
}
}
// KeyValuePair represents a pair of expressions that serve as a single item
// within a map or object definition construct.
type KeyValuePair struct {
Key Expression
Value Expression
}

View File

@ -648,6 +648,18 @@ func (e *ObjectConsExpr) StartRange() hcl.Range {
return e.OpenRange
}
// Implementation for hcl.ExprMap
func (e *ObjectConsExpr) ExprMap() []hcl.KeyValuePair {
ret := make([]hcl.KeyValuePair, len(e.Items))
for i, item := range e.Items {
ret[i] = hcl.KeyValuePair{
Key: item.KeyExpr,
Value: item.ValueExpr,
}
}
return ret
}
// ForExpr represents iteration constructs:
//
// tuple = [for i, v in list: upper(v) if i > 2]

View File

@ -571,3 +571,23 @@ func (e *expression) ExprList() []hcl.Expression {
return nil
}
}
// Implementation for hcl.ExprMap.
func (e *expression) ExprMap() []hcl.KeyValuePair {
switch v := e.src.(type) {
case *objectVal:
ret := make([]hcl.KeyValuePair, len(v.Attrs))
for i, jsonAttr := range v.Attrs {
ret[i] = hcl.KeyValuePair{
Key: &expression{src: &stringVal{
Value: jsonAttr.Name,
SrcRange: jsonAttr.NameRange,
}},
Value: &expression{src: jsonAttr.Value},
}
}
return ret
default:
return nil
}
}