6c4344623b
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.
269 lines
6.0 KiB
Go
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
|
|
}
|