hcl/traversal_for_expr.go
Martin Atkins 6c4344623b Unfold the "hcl" directory up into the root
The main HCL package is more visible this way, and so it's easier than
having to pick it out from dozens of other package directories.
2019-09-09 16:08:19 -07:00

125 lines
4.6 KiB
Go

package hcl
// AbsTraversalForExpr attempts to interpret the given expression as
// an absolute traversal, or returns error diagnostic(s) if that is
// not possible for the given expression.
//
// 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. 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
// needs to see the the name of the variable and subsequent
// attributes/indexes itself, for example to allow users to give references
// to the variables themselves rather than to their values. An implementer
// of this function should at least support attribute and index steps.
func AbsTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
type asTraversal interface {
AsTraversal() Traversal
}
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
}
}
return nil, Diagnostics{
&Diagnostic{
Severity: DiagError,
Summary: "Invalid expression",
Detail: "A single static variable reference is required: only attribute access and indexing with constant keys. No calculations, function calls, template expressions, etc are allowed here.",
Subject: expr.Range().Ptr(),
},
}
}
// RelTraversalForExpr is similar to AbsTraversalForExpr but it returns
// a relative traversal instead. Due to the nature of HCL expressions, the
// first element of the returned traversal is always a TraverseAttr, and
// then it will be followed by zero or more other expressions.
//
// Any expression accepted by AbsTraversalForExpr is also accepted by
// RelTraversalForExpr.
func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
traversal, diags := AbsTraversalForExpr(expr)
if len(traversal) > 0 {
ret := make(Traversal, len(traversal))
copy(ret, traversal)
root := traversal[0].(TraverseRoot)
ret[0] = TraverseAttr{
Name: root.Name,
SrcRange: root.SrcRange,
}
return ret, diags
}
return traversal, diags
}
// ExprAsKeyword attempts to interpret the given expression as a static keyword,
// returning the keyword string if possible, and the empty string if not.
//
// A static keyword, for the sake of this function, is a single identifier.
// For example, the following attribute has an expression that would produce
// the keyword "foo":
//
// example = foo
//
// This function is a variant of AbsTraversalForExpr, which uses the same
// interface on the given expression. This helper constrains the result
// further by requiring only a single root identifier.
//
// This function is intended to be used with the following idiom, to recognize
// situations where one of a fixed set of keywords is required and arbitrary
// expressions are not allowed:
//
// switch hcl.ExprAsKeyword(expr) {
// case "allow":
// // (take suitable action for keyword "allow")
// case "deny":
// // (take suitable action for keyword "deny")
// default:
// diags = append(diags, &hcl.Diagnostic{
// // ... "invalid keyword" diagnostic message ...
// })
// }
//
// The above approach will generate the same message for both the use of an
// unrecognized keyword and for not using a keyword at all, which is usually
// reasonable if the message specifies that the given value must be a keyword
// from that fixed list.
//
// Note that in the native syntax the keywords "true", "false", and "null" are
// recognized as literal values during parsing and so these reserved words
// cannot not be accepted as keywords by this function.
//
// Since interpreting an expression as a keyword bypasses usual expression
// evaluation, it should be used sparingly for situations where e.g. one of
// a fixed set of keywords is used in a structural way in a special attribute
// to affect the further processing of a block.
func ExprAsKeyword(expr Expression) string {
type asTraversal interface {
AsTraversal() Traversal
}
physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool {
_, supported := expr.(asTraversal)
return supported
})
if asT, supported := physExpr.(asTraversal); supported {
if traversal := asT.AsTraversal(); len(traversal) == 1 {
return traversal.RootName()
}
}
return ""
}