2018-11-10 17:36:26 +00:00
|
|
|
package hclpack
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2018-11-11 16:26:37 +00:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
|
|
|
|
2018-11-10 17:36:26 +00:00
|
|
|
"github.com/hashicorp/hcl2/hcl"
|
|
|
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Expression is an implementation of hcl.Expression in terms of some raw
|
|
|
|
// expression source code. The methods of this type will first parse the
|
|
|
|
// source code and then pass the call through to the real expression that
|
|
|
|
// is produced.
|
|
|
|
type Expression struct {
|
|
|
|
// Source is the raw source code of the expression, which should be parsed
|
|
|
|
// as the syntax specified by SourceType.
|
|
|
|
Source []byte
|
|
|
|
SourceType ExprSourceType
|
|
|
|
|
|
|
|
// Range_ and StartRange_ describe the physical extents of the expression
|
|
|
|
// in the original source code. SourceRange_ is its entire range while
|
|
|
|
// StartRange is just the tokens that introduce the expression type. For
|
|
|
|
// simple expression types, SourceRange and StartRange are identical.
|
|
|
|
Range_, StartRange_ hcl.Range
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ hcl.Expression = (*Expression)(nil)
|
|
|
|
|
|
|
|
// Value implements the Value method of hcl.Expression but with the additional
|
|
|
|
// step of first parsing the expression source code. This implementation is
|
|
|
|
// unusual in that it can potentially return syntax errors, whereas other
|
|
|
|
// Value implementations usually work with already-parsed expressions.
|
|
|
|
func (e *Expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
expr, diags := e.Parse()
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return cty.DynamicVal, diags
|
|
|
|
}
|
|
|
|
|
|
|
|
val, moreDiags := expr.Value(ctx)
|
|
|
|
diags = append(diags, moreDiags...)
|
|
|
|
return val, diags
|
|
|
|
}
|
|
|
|
|
|
|
|
// Variables implements the Variables method of hcl.Expression but with the
|
|
|
|
// additional step of first parsing the expression source code.
|
|
|
|
//
|
|
|
|
// Since this method cannot return errors, it will return a nil slice if
|
|
|
|
// parsing fails, indicating that no variables are present. This is okay in
|
|
|
|
// practice because a subsequent call to Value would fail with syntax errors
|
|
|
|
// regardless of what variables are in the context.
|
|
|
|
func (e *Expression) Variables() []hcl.Traversal {
|
|
|
|
expr, diags := e.Parse()
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return expr.Variables()
|
|
|
|
}
|
|
|
|
|
2018-11-11 16:34:44 +00:00
|
|
|
// UnwrapExpression parses and returns the underlying expression, if possible.
|
|
|
|
//
|
|
|
|
// This is essentially the same as Parse but without the ability to return an
|
|
|
|
// error; it is here only to support the static analysis facilities in the
|
|
|
|
// main HCL package (ExprList, ExprMap, etc). If any error is encountered
|
|
|
|
// during parsing, the result is a static expression that always returns
|
|
|
|
// cty.DynamicVal.
|
|
|
|
//
|
|
|
|
// This function does not impose any further conversions on the underlying
|
|
|
|
// expression, so the result may still not be suitable for the static analysis
|
|
|
|
// functions, depending on the source type of the expression and thus what
|
|
|
|
// type of physical expression it becomes after decoding.
|
|
|
|
func (e *Expression) UnwrapExpression() hcl.Expression {
|
|
|
|
expr, diags := e.Parse()
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return hcl.StaticExpr(cty.DynamicVal, e.Range_)
|
|
|
|
}
|
|
|
|
return expr
|
|
|
|
}
|
|
|
|
|
2018-11-10 17:36:26 +00:00
|
|
|
func (e *Expression) Range() hcl.Range {
|
|
|
|
return e.Range_
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Expression) StartRange() hcl.Range {
|
|
|
|
return e.StartRange_
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse attempts to parse the source code of the receiving expression using
|
|
|
|
// its indicated source type, returning the expression if possible and any
|
|
|
|
// diagnostics produced during parsing.
|
|
|
|
func (e *Expression) Parse() (hcl.Expression, hcl.Diagnostics) {
|
|
|
|
switch e.SourceType {
|
|
|
|
case ExprNative:
|
|
|
|
return hclsyntax.ParseExpression(e.Source, e.Range_.Filename, e.Range_.Start)
|
|
|
|
case ExprTemplate:
|
|
|
|
return hclsyntax.ParseTemplate(e.Source, e.Range_.Filename, e.Range_.Start)
|
2018-11-11 16:26:37 +00:00
|
|
|
case ExprLiteralJSON:
|
|
|
|
ty, err := ctyjson.ImpliedType(e.Source)
|
|
|
|
if err != nil {
|
|
|
|
return nil, hcl.Diagnostics{
|
|
|
|
{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Invalid JSON value",
|
|
|
|
Detail: fmt.Sprintf("The JSON representation of this expression is invalid: %s.", err),
|
|
|
|
Subject: &e.Range_,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val, err := ctyjson.Unmarshal(e.Source, ty)
|
|
|
|
if err != nil {
|
|
|
|
return nil, hcl.Diagnostics{
|
|
|
|
{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Invalid JSON value",
|
|
|
|
Detail: fmt.Sprintf("The JSON representation of this expression is invalid: %s.", err),
|
|
|
|
Subject: &e.Range_,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return hcl.StaticExpr(val, e.Range_), nil
|
2018-11-10 17:36:26 +00:00
|
|
|
default:
|
|
|
|
// This should never happen for a valid Expression.
|
|
|
|
return nil, hcl.Diagnostics{
|
|
|
|
{
|
|
|
|
Severity: hcl.DiagError,
|
|
|
|
Summary: "Invalid expression source type",
|
|
|
|
Detail: fmt.Sprintf("Packed version of this expression has an invalid source type %s. This is always a bug.", e.SourceType),
|
|
|
|
Subject: &e.Range_,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-11 03:25:19 +00:00
|
|
|
func (e *Expression) addRanges(rngs map[hcl.Range]struct{}) {
|
|
|
|
rngs[e.Range_] = struct{}{}
|
|
|
|
rngs[e.StartRange_] = struct{}{}
|
|
|
|
}
|
|
|
|
|
2018-11-10 17:36:26 +00:00
|
|
|
// ExprSourceType defines the syntax type used for an expression's source code,
|
|
|
|
// which is then used to select a suitable parser for it when evaluating.
|
|
|
|
type ExprSourceType rune
|
|
|
|
|
|
|
|
//go:generate stringer -type ExprSourceType
|
|
|
|
|
|
|
|
const (
|
|
|
|
// ExprNative indicates that an expression must be parsed as native
|
|
|
|
// expression syntax, with hclsyntax.ParseExpression.
|
|
|
|
ExprNative ExprSourceType = 'N'
|
|
|
|
|
2018-11-11 16:26:37 +00:00
|
|
|
// ExprTemplate indicates that an expression must be parsed as native
|
2018-11-10 17:36:26 +00:00
|
|
|
// template syntax, with hclsyntax.ParseTemplate.
|
|
|
|
ExprTemplate ExprSourceType = 'T'
|
2018-11-11 16:26:37 +00:00
|
|
|
|
|
|
|
// ExprLiteralJSON indicates that an expression must be parsed as JSON and
|
|
|
|
// treated literally, using cty/json. This can be used when populating
|
|
|
|
// literal attribute values from a non-HCL source.
|
|
|
|
ExprLiteralJSON ExprSourceType = 'L'
|
2018-11-10 17:36:26 +00:00
|
|
|
)
|