hcl/hclsyntax/expression_ops.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

269 lines
6.0 KiB
Go

package hclsyntax
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/function/stdlib"
)
type Operation struct {
Impl function.Function
Type cty.Type
}
var (
OpLogicalOr = &Operation{
Impl: stdlib.OrFunc,
Type: cty.Bool,
}
OpLogicalAnd = &Operation{
Impl: stdlib.AndFunc,
Type: cty.Bool,
}
OpLogicalNot = &Operation{
Impl: stdlib.NotFunc,
Type: cty.Bool,
}
OpEqual = &Operation{
Impl: stdlib.EqualFunc,
Type: cty.Bool,
}
OpNotEqual = &Operation{
Impl: stdlib.NotEqualFunc,
Type: cty.Bool,
}
OpGreaterThan = &Operation{
Impl: stdlib.GreaterThanFunc,
Type: cty.Bool,
}
OpGreaterThanOrEqual = &Operation{
Impl: stdlib.GreaterThanOrEqualToFunc,
Type: cty.Bool,
}
OpLessThan = &Operation{
Impl: stdlib.LessThanFunc,
Type: cty.Bool,
}
OpLessThanOrEqual = &Operation{
Impl: stdlib.LessThanOrEqualToFunc,
Type: cty.Bool,
}
OpAdd = &Operation{
Impl: stdlib.AddFunc,
Type: cty.Number,
}
OpSubtract = &Operation{
Impl: stdlib.SubtractFunc,
Type: cty.Number,
}
OpMultiply = &Operation{
Impl: stdlib.MultiplyFunc,
Type: cty.Number,
}
OpDivide = &Operation{
Impl: stdlib.DivideFunc,
Type: cty.Number,
}
OpModulo = &Operation{
Impl: stdlib.ModuloFunc,
Type: cty.Number,
}
OpNegate = &Operation{
Impl: stdlib.NegateFunc,
Type: cty.Number,
}
)
var binaryOps []map[TokenType]*Operation
func init() {
// This operation table maps from the operator's token type
// to the AST operation type. All expressions produced from
// binary operators are BinaryOp nodes.
//
// Binary operator groups are listed in order of precedence, with
// the *lowest* precedence first. Operators within the same group
// have left-to-right associativity.
binaryOps = []map[TokenType]*Operation{
{
TokenOr: OpLogicalOr,
},
{
TokenAnd: OpLogicalAnd,
},
{
TokenEqualOp: OpEqual,
TokenNotEqual: OpNotEqual,
},
{
TokenGreaterThan: OpGreaterThan,
TokenGreaterThanEq: OpGreaterThanOrEqual,
TokenLessThan: OpLessThan,
TokenLessThanEq: OpLessThanOrEqual,
},
{
TokenPlus: OpAdd,
TokenMinus: OpSubtract,
},
{
TokenStar: OpMultiply,
TokenSlash: OpDivide,
TokenPercent: OpModulo,
},
}
}
type BinaryOpExpr struct {
LHS Expression
Op *Operation
RHS Expression
SrcRange hcl.Range
}
func (e *BinaryOpExpr) walkChildNodes(w internalWalkFunc) {
w(e.LHS)
w(e.RHS)
}
func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
impl := e.Op.Impl // assumed to be a function taking exactly two arguments
params := impl.Params()
lhsParam := params[0]
rhsParam := params[1]
var diags hcl.Diagnostics
givenLHSVal, lhsDiags := e.LHS.Value(ctx)
givenRHSVal, rhsDiags := e.RHS.Value(ctx)
diags = append(diags, lhsDiags...)
diags = append(diags, rhsDiags...)
lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid operand",
Detail: fmt.Sprintf("Unsuitable value for left operand: %s.", err),
Subject: e.LHS.Range().Ptr(),
Context: &e.SrcRange,
Expression: e.LHS,
EvalContext: ctx,
})
}
rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid operand",
Detail: fmt.Sprintf("Unsuitable value for right operand: %s.", err),
Subject: e.RHS.Range().Ptr(),
Context: &e.SrcRange,
Expression: e.RHS,
EvalContext: ctx,
})
}
if diags.HasErrors() {
// Don't actually try the call if we have errors already, since the
// this will probably just produce a confusing duplicative diagnostic.
return cty.UnknownVal(e.Op.Type), diags
}
args := []cty.Value{lhsVal, rhsVal}
result, err := impl.Call(args)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
// FIXME: This diagnostic is useless.
Severity: hcl.DiagError,
Summary: "Operation failed",
Detail: fmt.Sprintf("Error during operation: %s.", err),
Subject: &e.SrcRange,
Expression: e,
EvalContext: ctx,
})
return cty.UnknownVal(e.Op.Type), diags
}
return result, diags
}
func (e *BinaryOpExpr) Range() hcl.Range {
return e.SrcRange
}
func (e *BinaryOpExpr) StartRange() hcl.Range {
return e.LHS.StartRange()
}
type UnaryOpExpr struct {
Op *Operation
Val Expression
SrcRange hcl.Range
SymbolRange hcl.Range
}
func (e *UnaryOpExpr) walkChildNodes(w internalWalkFunc) {
w(e.Val)
}
func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
impl := e.Op.Impl // assumed to be a function taking exactly one argument
params := impl.Params()
param := params[0]
givenVal, diags := e.Val.Value(ctx)
val, err := convert.Convert(givenVal, param.Type)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid operand",
Detail: fmt.Sprintf("Unsuitable value for unary operand: %s.", err),
Subject: e.Val.Range().Ptr(),
Context: &e.SrcRange,
Expression: e.Val,
EvalContext: ctx,
})
}
if diags.HasErrors() {
// Don't actually try the call if we have errors already, since the
// this will probably just produce a confusing duplicative diagnostic.
return cty.UnknownVal(e.Op.Type), diags
}
args := []cty.Value{val}
result, err := impl.Call(args)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
// FIXME: This diagnostic is useless.
Severity: hcl.DiagError,
Summary: "Operation failed",
Detail: fmt.Sprintf("Error during operation: %s.", err),
Subject: &e.SrcRange,
Expression: e,
EvalContext: ctx,
})
return cty.UnknownVal(e.Op.Type), diags
}
return result, diags
}
func (e *UnaryOpExpr) Range() hcl.Range {
return e.SrcRange
}
func (e *UnaryOpExpr) StartRange() hcl.Range {
return e.SymbolRange
}