1package hclsyntax 2 3import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl2/hcl" 7 "github.com/zclconf/go-cty/cty" 8 "github.com/zclconf/go-cty/cty/convert" 9 "github.com/zclconf/go-cty/cty/function" 10 "github.com/zclconf/go-cty/cty/function/stdlib" 11) 12 13type Operation struct { 14 Impl function.Function 15 Type cty.Type 16} 17 18var ( 19 OpLogicalOr = &Operation{ 20 Impl: stdlib.OrFunc, 21 Type: cty.Bool, 22 } 23 OpLogicalAnd = &Operation{ 24 Impl: stdlib.AndFunc, 25 Type: cty.Bool, 26 } 27 OpLogicalNot = &Operation{ 28 Impl: stdlib.NotFunc, 29 Type: cty.Bool, 30 } 31 32 OpEqual = &Operation{ 33 Impl: stdlib.EqualFunc, 34 Type: cty.Bool, 35 } 36 OpNotEqual = &Operation{ 37 Impl: stdlib.NotEqualFunc, 38 Type: cty.Bool, 39 } 40 41 OpGreaterThan = &Operation{ 42 Impl: stdlib.GreaterThanFunc, 43 Type: cty.Bool, 44 } 45 OpGreaterThanOrEqual = &Operation{ 46 Impl: stdlib.GreaterThanOrEqualToFunc, 47 Type: cty.Bool, 48 } 49 OpLessThan = &Operation{ 50 Impl: stdlib.LessThanFunc, 51 Type: cty.Bool, 52 } 53 OpLessThanOrEqual = &Operation{ 54 Impl: stdlib.LessThanOrEqualToFunc, 55 Type: cty.Bool, 56 } 57 58 OpAdd = &Operation{ 59 Impl: stdlib.AddFunc, 60 Type: cty.Number, 61 } 62 OpSubtract = &Operation{ 63 Impl: stdlib.SubtractFunc, 64 Type: cty.Number, 65 } 66 OpMultiply = &Operation{ 67 Impl: stdlib.MultiplyFunc, 68 Type: cty.Number, 69 } 70 OpDivide = &Operation{ 71 Impl: stdlib.DivideFunc, 72 Type: cty.Number, 73 } 74 OpModulo = &Operation{ 75 Impl: stdlib.ModuloFunc, 76 Type: cty.Number, 77 } 78 OpNegate = &Operation{ 79 Impl: stdlib.NegateFunc, 80 Type: cty.Number, 81 } 82) 83 84var binaryOps []map[TokenType]*Operation 85 86func init() { 87 // This operation table maps from the operator's token type 88 // to the AST operation type. All expressions produced from 89 // binary operators are BinaryOp nodes. 90 // 91 // Binary operator groups are listed in order of precedence, with 92 // the *lowest* precedence first. Operators within the same group 93 // have left-to-right associativity. 94 binaryOps = []map[TokenType]*Operation{ 95 { 96 TokenOr: OpLogicalOr, 97 }, 98 { 99 TokenAnd: OpLogicalAnd, 100 }, 101 { 102 TokenEqualOp: OpEqual, 103 TokenNotEqual: OpNotEqual, 104 }, 105 { 106 TokenGreaterThan: OpGreaterThan, 107 TokenGreaterThanEq: OpGreaterThanOrEqual, 108 TokenLessThan: OpLessThan, 109 TokenLessThanEq: OpLessThanOrEqual, 110 }, 111 { 112 TokenPlus: OpAdd, 113 TokenMinus: OpSubtract, 114 }, 115 { 116 TokenStar: OpMultiply, 117 TokenSlash: OpDivide, 118 TokenPercent: OpModulo, 119 }, 120 } 121} 122 123type BinaryOpExpr struct { 124 LHS Expression 125 Op *Operation 126 RHS Expression 127 128 SrcRange hcl.Range 129} 130 131func (e *BinaryOpExpr) walkChildNodes(w internalWalkFunc) { 132 w(e.LHS) 133 w(e.RHS) 134} 135 136func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 137 impl := e.Op.Impl // assumed to be a function taking exactly two arguments 138 params := impl.Params() 139 lhsParam := params[0] 140 rhsParam := params[1] 141 142 var diags hcl.Diagnostics 143 144 givenLHSVal, lhsDiags := e.LHS.Value(ctx) 145 givenRHSVal, rhsDiags := e.RHS.Value(ctx) 146 diags = append(diags, lhsDiags...) 147 diags = append(diags, rhsDiags...) 148 149 lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type) 150 if err != nil { 151 diags = append(diags, &hcl.Diagnostic{ 152 Severity: hcl.DiagError, 153 Summary: "Invalid operand", 154 Detail: fmt.Sprintf("Unsuitable value for left operand: %s.", err), 155 Subject: e.LHS.Range().Ptr(), 156 Context: &e.SrcRange, 157 Expression: e.LHS, 158 EvalContext: ctx, 159 }) 160 } 161 rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type) 162 if err != nil { 163 diags = append(diags, &hcl.Diagnostic{ 164 Severity: hcl.DiagError, 165 Summary: "Invalid operand", 166 Detail: fmt.Sprintf("Unsuitable value for right operand: %s.", err), 167 Subject: e.RHS.Range().Ptr(), 168 Context: &e.SrcRange, 169 Expression: e.RHS, 170 EvalContext: ctx, 171 }) 172 } 173 174 if diags.HasErrors() { 175 // Don't actually try the call if we have errors already, since the 176 // this will probably just produce a confusing duplicative diagnostic. 177 return cty.UnknownVal(e.Op.Type), diags 178 } 179 180 args := []cty.Value{lhsVal, rhsVal} 181 result, err := impl.Call(args) 182 if err != nil { 183 diags = append(diags, &hcl.Diagnostic{ 184 // FIXME: This diagnostic is useless. 185 Severity: hcl.DiagError, 186 Summary: "Operation failed", 187 Detail: fmt.Sprintf("Error during operation: %s.", err), 188 Subject: &e.SrcRange, 189 Expression: e, 190 EvalContext: ctx, 191 }) 192 return cty.UnknownVal(e.Op.Type), diags 193 } 194 195 return result, diags 196} 197 198func (e *BinaryOpExpr) Range() hcl.Range { 199 return e.SrcRange 200} 201 202func (e *BinaryOpExpr) StartRange() hcl.Range { 203 return e.LHS.StartRange() 204} 205 206type UnaryOpExpr struct { 207 Op *Operation 208 Val Expression 209 210 SrcRange hcl.Range 211 SymbolRange hcl.Range 212} 213 214func (e *UnaryOpExpr) walkChildNodes(w internalWalkFunc) { 215 w(e.Val) 216} 217 218func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 219 impl := e.Op.Impl // assumed to be a function taking exactly one argument 220 params := impl.Params() 221 param := params[0] 222 223 givenVal, diags := e.Val.Value(ctx) 224 225 val, err := convert.Convert(givenVal, param.Type) 226 if err != nil { 227 diags = append(diags, &hcl.Diagnostic{ 228 Severity: hcl.DiagError, 229 Summary: "Invalid operand", 230 Detail: fmt.Sprintf("Unsuitable value for unary operand: %s.", err), 231 Subject: e.Val.Range().Ptr(), 232 Context: &e.SrcRange, 233 Expression: e.Val, 234 EvalContext: ctx, 235 }) 236 } 237 238 if diags.HasErrors() { 239 // Don't actually try the call if we have errors already, since the 240 // this will probably just produce a confusing duplicative diagnostic. 241 return cty.UnknownVal(e.Op.Type), diags 242 } 243 244 args := []cty.Value{val} 245 result, err := impl.Call(args) 246 if err != nil { 247 diags = append(diags, &hcl.Diagnostic{ 248 // FIXME: This diagnostic is useless. 249 Severity: hcl.DiagError, 250 Summary: "Operation failed", 251 Detail: fmt.Sprintf("Error during operation: %s.", err), 252 Subject: &e.SrcRange, 253 Expression: e, 254 EvalContext: ctx, 255 }) 256 return cty.UnknownVal(e.Op.Type), diags 257 } 258 259 return result, diags 260} 261 262func (e *UnaryOpExpr) Range() hcl.Range { 263 return e.SrcRange 264} 265 266func (e *UnaryOpExpr) StartRange() hcl.Range { 267 return e.SymbolRange 268} 269