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 }