1package stdlib 2 3import ( 4 "fmt" 5 "math" 6 "math/big" 7 8 "github.com/zclconf/go-cty/cty" 9 "github.com/zclconf/go-cty/cty/function" 10 "github.com/zclconf/go-cty/cty/gocty" 11) 12 13var AbsoluteFunc = function.New(&function.Spec{ 14 Params: []function.Parameter{ 15 { 16 Name: "num", 17 Type: cty.Number, 18 AllowDynamicType: true, 19 AllowMarked: true, 20 }, 21 }, 22 Type: function.StaticReturnType(cty.Number), 23 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 24 return args[0].Absolute(), nil 25 }, 26}) 27 28var AddFunc = function.New(&function.Spec{ 29 Params: []function.Parameter{ 30 { 31 Name: "a", 32 Type: cty.Number, 33 AllowDynamicType: true, 34 }, 35 { 36 Name: "b", 37 Type: cty.Number, 38 AllowDynamicType: true, 39 }, 40 }, 41 Type: function.StaticReturnType(cty.Number), 42 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 43 // big.Float.Add can panic if the input values are opposing infinities, 44 // so we must catch that here in order to remain within 45 // the cty Function abstraction. 46 defer func() { 47 if r := recover(); r != nil { 48 if _, ok := r.(big.ErrNaN); ok { 49 ret = cty.NilVal 50 err = fmt.Errorf("can't compute sum of opposing infinities") 51 } else { 52 // not a panic we recognize 53 panic(r) 54 } 55 } 56 }() 57 return args[0].Add(args[1]), nil 58 }, 59}) 60 61var SubtractFunc = function.New(&function.Spec{ 62 Params: []function.Parameter{ 63 { 64 Name: "a", 65 Type: cty.Number, 66 AllowDynamicType: true, 67 }, 68 { 69 Name: "b", 70 Type: cty.Number, 71 AllowDynamicType: true, 72 }, 73 }, 74 Type: function.StaticReturnType(cty.Number), 75 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 76 // big.Float.Sub can panic if the input values are infinities, 77 // so we must catch that here in order to remain within 78 // the cty Function abstraction. 79 defer func() { 80 if r := recover(); r != nil { 81 if _, ok := r.(big.ErrNaN); ok { 82 ret = cty.NilVal 83 err = fmt.Errorf("can't subtract infinity from itself") 84 } else { 85 // not a panic we recognize 86 panic(r) 87 } 88 } 89 }() 90 return args[0].Subtract(args[1]), nil 91 }, 92}) 93 94var MultiplyFunc = function.New(&function.Spec{ 95 Params: []function.Parameter{ 96 { 97 Name: "a", 98 Type: cty.Number, 99 AllowDynamicType: true, 100 }, 101 { 102 Name: "b", 103 Type: cty.Number, 104 AllowDynamicType: true, 105 }, 106 }, 107 Type: function.StaticReturnType(cty.Number), 108 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 109 // big.Float.Mul can panic if the input values are both zero or both 110 // infinity, so we must catch that here in order to remain within 111 // the cty Function abstraction. 112 defer func() { 113 if r := recover(); r != nil { 114 if _, ok := r.(big.ErrNaN); ok { 115 ret = cty.NilVal 116 err = fmt.Errorf("can't multiply zero by infinity") 117 } else { 118 // not a panic we recognize 119 panic(r) 120 } 121 } 122 }() 123 124 return args[0].Multiply(args[1]), nil 125 }, 126}) 127 128var DivideFunc = function.New(&function.Spec{ 129 Params: []function.Parameter{ 130 { 131 Name: "a", 132 Type: cty.Number, 133 AllowDynamicType: true, 134 }, 135 { 136 Name: "b", 137 Type: cty.Number, 138 AllowDynamicType: true, 139 }, 140 }, 141 Type: function.StaticReturnType(cty.Number), 142 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 143 // big.Float.Quo can panic if the input values are both zero or both 144 // infinity, so we must catch that here in order to remain within 145 // the cty Function abstraction. 146 defer func() { 147 if r := recover(); r != nil { 148 if _, ok := r.(big.ErrNaN); ok { 149 ret = cty.NilVal 150 err = fmt.Errorf("can't divide zero by zero or infinity by infinity") 151 } else { 152 // not a panic we recognize 153 panic(r) 154 } 155 } 156 }() 157 158 return args[0].Divide(args[1]), nil 159 }, 160}) 161 162var ModuloFunc = function.New(&function.Spec{ 163 Params: []function.Parameter{ 164 { 165 Name: "a", 166 Type: cty.Number, 167 AllowDynamicType: true, 168 }, 169 { 170 Name: "b", 171 Type: cty.Number, 172 AllowDynamicType: true, 173 }, 174 }, 175 Type: function.StaticReturnType(cty.Number), 176 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 177 // big.Float.Mul can panic if the input values are both zero or both 178 // infinity, so we must catch that here in order to remain within 179 // the cty Function abstraction. 180 defer func() { 181 if r := recover(); r != nil { 182 if _, ok := r.(big.ErrNaN); ok { 183 ret = cty.NilVal 184 err = fmt.Errorf("can't use modulo with zero and infinity") 185 } else { 186 // not a panic we recognize 187 panic(r) 188 } 189 } 190 }() 191 192 return args[0].Modulo(args[1]), nil 193 }, 194}) 195 196var GreaterThanFunc = function.New(&function.Spec{ 197 Params: []function.Parameter{ 198 { 199 Name: "a", 200 Type: cty.Number, 201 AllowDynamicType: true, 202 AllowMarked: true, 203 }, 204 { 205 Name: "b", 206 Type: cty.Number, 207 AllowDynamicType: true, 208 AllowMarked: true, 209 }, 210 }, 211 Type: function.StaticReturnType(cty.Bool), 212 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 213 return args[0].GreaterThan(args[1]), nil 214 }, 215}) 216 217var GreaterThanOrEqualToFunc = function.New(&function.Spec{ 218 Params: []function.Parameter{ 219 { 220 Name: "a", 221 Type: cty.Number, 222 AllowDynamicType: true, 223 AllowMarked: true, 224 }, 225 { 226 Name: "b", 227 Type: cty.Number, 228 AllowDynamicType: true, 229 AllowMarked: true, 230 }, 231 }, 232 Type: function.StaticReturnType(cty.Bool), 233 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 234 return args[0].GreaterThanOrEqualTo(args[1]), nil 235 }, 236}) 237 238var LessThanFunc = function.New(&function.Spec{ 239 Params: []function.Parameter{ 240 { 241 Name: "a", 242 Type: cty.Number, 243 AllowDynamicType: true, 244 AllowMarked: true, 245 }, 246 { 247 Name: "b", 248 Type: cty.Number, 249 AllowDynamicType: true, 250 AllowMarked: true, 251 }, 252 }, 253 Type: function.StaticReturnType(cty.Bool), 254 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 255 return args[0].LessThan(args[1]), nil 256 }, 257}) 258 259var LessThanOrEqualToFunc = function.New(&function.Spec{ 260 Params: []function.Parameter{ 261 { 262 Name: "a", 263 Type: cty.Number, 264 AllowDynamicType: true, 265 AllowMarked: true, 266 }, 267 { 268 Name: "b", 269 Type: cty.Number, 270 AllowDynamicType: true, 271 AllowMarked: true, 272 }, 273 }, 274 Type: function.StaticReturnType(cty.Bool), 275 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 276 return args[0].LessThanOrEqualTo(args[1]), nil 277 }, 278}) 279 280var NegateFunc = function.New(&function.Spec{ 281 Params: []function.Parameter{ 282 { 283 Name: "num", 284 Type: cty.Number, 285 AllowDynamicType: true, 286 AllowMarked: true, 287 }, 288 }, 289 Type: function.StaticReturnType(cty.Number), 290 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 291 return args[0].Negate(), nil 292 }, 293}) 294 295var MinFunc = function.New(&function.Spec{ 296 Params: []function.Parameter{}, 297 VarParam: &function.Parameter{ 298 Name: "numbers", 299 Type: cty.Number, 300 AllowDynamicType: true, 301 }, 302 Type: function.StaticReturnType(cty.Number), 303 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 304 if len(args) == 0 { 305 return cty.NilVal, fmt.Errorf("must pass at least one number") 306 } 307 308 min := cty.PositiveInfinity 309 for _, num := range args { 310 if num.LessThan(min).True() { 311 min = num 312 } 313 } 314 315 return min, nil 316 }, 317}) 318 319var MaxFunc = function.New(&function.Spec{ 320 Params: []function.Parameter{}, 321 VarParam: &function.Parameter{ 322 Name: "numbers", 323 Type: cty.Number, 324 AllowDynamicType: true, 325 }, 326 Type: function.StaticReturnType(cty.Number), 327 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 328 if len(args) == 0 { 329 return cty.NilVal, fmt.Errorf("must pass at least one number") 330 } 331 332 max := cty.NegativeInfinity 333 for _, num := range args { 334 if num.GreaterThan(max).True() { 335 max = num 336 } 337 } 338 339 return max, nil 340 }, 341}) 342 343var IntFunc = function.New(&function.Spec{ 344 Params: []function.Parameter{ 345 { 346 Name: "num", 347 Type: cty.Number, 348 AllowDynamicType: true, 349 }, 350 }, 351 Type: function.StaticReturnType(cty.Number), 352 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 353 bf := args[0].AsBigFloat() 354 if bf.IsInt() { 355 return args[0], nil 356 } 357 bi, _ := bf.Int(nil) 358 bf = (&big.Float{}).SetInt(bi) 359 return cty.NumberVal(bf), nil 360 }, 361}) 362 363// CeilFunc is a function that returns the closest whole number greater 364// than or equal to the given value. 365var CeilFunc = function.New(&function.Spec{ 366 Params: []function.Parameter{ 367 { 368 Name: "num", 369 Type: cty.Number, 370 }, 371 }, 372 Type: function.StaticReturnType(cty.Number), 373 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 374 var val float64 375 if err := gocty.FromCtyValue(args[0], &val); err != nil { 376 return cty.UnknownVal(cty.String), err 377 } 378 if math.IsInf(val, 0) { 379 return cty.NumberFloatVal(val), nil 380 } 381 return cty.NumberIntVal(int64(math.Ceil(val))), nil 382 }, 383}) 384 385// FloorFunc is a function that returns the closest whole number lesser 386// than or equal to the given value. 387var FloorFunc = function.New(&function.Spec{ 388 Params: []function.Parameter{ 389 { 390 Name: "num", 391 Type: cty.Number, 392 }, 393 }, 394 Type: function.StaticReturnType(cty.Number), 395 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 396 var val float64 397 if err := gocty.FromCtyValue(args[0], &val); err != nil { 398 return cty.UnknownVal(cty.String), err 399 } 400 if math.IsInf(val, 0) { 401 return cty.NumberFloatVal(val), nil 402 } 403 return cty.NumberIntVal(int64(math.Floor(val))), nil 404 }, 405}) 406 407// LogFunc is a function that returns the logarithm of a given number in a given base. 408var LogFunc = function.New(&function.Spec{ 409 Params: []function.Parameter{ 410 { 411 Name: "num", 412 Type: cty.Number, 413 }, 414 { 415 Name: "base", 416 Type: cty.Number, 417 }, 418 }, 419 Type: function.StaticReturnType(cty.Number), 420 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 421 var num float64 422 if err := gocty.FromCtyValue(args[0], &num); err != nil { 423 return cty.UnknownVal(cty.String), err 424 } 425 426 var base float64 427 if err := gocty.FromCtyValue(args[1], &base); err != nil { 428 return cty.UnknownVal(cty.String), err 429 } 430 431 return cty.NumberFloatVal(math.Log(num) / math.Log(base)), nil 432 }, 433}) 434 435// PowFunc is a function that returns the logarithm of a given number in a given base. 436var PowFunc = function.New(&function.Spec{ 437 Params: []function.Parameter{ 438 { 439 Name: "num", 440 Type: cty.Number, 441 }, 442 { 443 Name: "power", 444 Type: cty.Number, 445 }, 446 }, 447 Type: function.StaticReturnType(cty.Number), 448 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 449 var num float64 450 if err := gocty.FromCtyValue(args[0], &num); err != nil { 451 return cty.UnknownVal(cty.String), err 452 } 453 454 var power float64 455 if err := gocty.FromCtyValue(args[1], &power); err != nil { 456 return cty.UnknownVal(cty.String), err 457 } 458 459 return cty.NumberFloatVal(math.Pow(num, power)), nil 460 }, 461}) 462 463// SignumFunc is a function that determines the sign of a number, returning a 464// number between -1 and 1 to represent the sign.. 465var SignumFunc = function.New(&function.Spec{ 466 Params: []function.Parameter{ 467 { 468 Name: "num", 469 Type: cty.Number, 470 }, 471 }, 472 Type: function.StaticReturnType(cty.Number), 473 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 474 var num int 475 if err := gocty.FromCtyValue(args[0], &num); err != nil { 476 return cty.UnknownVal(cty.String), err 477 } 478 switch { 479 case num < 0: 480 return cty.NumberIntVal(-1), nil 481 case num > 0: 482 return cty.NumberIntVal(+1), nil 483 default: 484 return cty.NumberIntVal(0), nil 485 } 486 }, 487}) 488 489// ParseIntFunc is a function that parses a string argument and returns an integer of the specified base. 490var ParseIntFunc = function.New(&function.Spec{ 491 Params: []function.Parameter{ 492 { 493 Name: "number", 494 Type: cty.DynamicPseudoType, 495 }, 496 { 497 Name: "base", 498 Type: cty.Number, 499 }, 500 }, 501 502 Type: func(args []cty.Value) (cty.Type, error) { 503 if !args[0].Type().Equals(cty.String) { 504 return cty.Number, function.NewArgErrorf(0, "first argument must be a string, not %s", args[0].Type().FriendlyName()) 505 } 506 return cty.Number, nil 507 }, 508 509 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 510 var numstr string 511 var base int 512 var err error 513 514 if err = gocty.FromCtyValue(args[0], &numstr); err != nil { 515 return cty.UnknownVal(cty.String), function.NewArgError(0, err) 516 } 517 518 if err = gocty.FromCtyValue(args[1], &base); err != nil { 519 return cty.UnknownVal(cty.Number), function.NewArgError(1, err) 520 } 521 522 if base < 2 || base > 62 { 523 return cty.UnknownVal(cty.Number), function.NewArgErrorf( 524 1, 525 "base must be a whole number between 2 and 62 inclusive", 526 ) 527 } 528 529 num, ok := (&big.Int{}).SetString(numstr, base) 530 if !ok { 531 return cty.UnknownVal(cty.Number), function.NewArgErrorf( 532 0, 533 "cannot parse %q as a base %d integer", 534 numstr, 535 base, 536 ) 537 } 538 539 parsedNum := cty.NumberVal((&big.Float{}).SetInt(num)) 540 541 return parsedNum, nil 542 }, 543}) 544 545// Absolute returns the magnitude of the given number, without its sign. 546// That is, it turns negative values into positive values. 547func Absolute(num cty.Value) (cty.Value, error) { 548 return AbsoluteFunc.Call([]cty.Value{num}) 549} 550 551// Add returns the sum of the two given numbers. 552func Add(a cty.Value, b cty.Value) (cty.Value, error) { 553 return AddFunc.Call([]cty.Value{a, b}) 554} 555 556// Subtract returns the difference between the two given numbers. 557func Subtract(a cty.Value, b cty.Value) (cty.Value, error) { 558 return SubtractFunc.Call([]cty.Value{a, b}) 559} 560 561// Multiply returns the product of the two given numbers. 562func Multiply(a cty.Value, b cty.Value) (cty.Value, error) { 563 return MultiplyFunc.Call([]cty.Value{a, b}) 564} 565 566// Divide returns a divided by b, where both a and b are numbers. 567func Divide(a cty.Value, b cty.Value) (cty.Value, error) { 568 return DivideFunc.Call([]cty.Value{a, b}) 569} 570 571// Negate returns the given number multipled by -1. 572func Negate(num cty.Value) (cty.Value, error) { 573 return NegateFunc.Call([]cty.Value{num}) 574} 575 576// LessThan returns true if a is less than b. 577func LessThan(a cty.Value, b cty.Value) (cty.Value, error) { 578 return LessThanFunc.Call([]cty.Value{a, b}) 579} 580 581// LessThanOrEqualTo returns true if a is less than b. 582func LessThanOrEqualTo(a cty.Value, b cty.Value) (cty.Value, error) { 583 return LessThanOrEqualToFunc.Call([]cty.Value{a, b}) 584} 585 586// GreaterThan returns true if a is less than b. 587func GreaterThan(a cty.Value, b cty.Value) (cty.Value, error) { 588 return GreaterThanFunc.Call([]cty.Value{a, b}) 589} 590 591// GreaterThanOrEqualTo returns true if a is less than b. 592func GreaterThanOrEqualTo(a cty.Value, b cty.Value) (cty.Value, error) { 593 return GreaterThanOrEqualToFunc.Call([]cty.Value{a, b}) 594} 595 596// Modulo returns the remainder of a divided by b under integer division, 597// where both a and b are numbers. 598func Modulo(a cty.Value, b cty.Value) (cty.Value, error) { 599 return ModuloFunc.Call([]cty.Value{a, b}) 600} 601 602// Min returns the minimum number from the given numbers. 603func Min(numbers ...cty.Value) (cty.Value, error) { 604 return MinFunc.Call(numbers) 605} 606 607// Max returns the maximum number from the given numbers. 608func Max(numbers ...cty.Value) (cty.Value, error) { 609 return MaxFunc.Call(numbers) 610} 611 612// Int removes the fractional component of the given number returning an 613// integer representing the whole number component, rounding towards zero. 614// For example, -1.5 becomes -1. 615// 616// If an infinity is passed to Int, an error is returned. 617func Int(num cty.Value) (cty.Value, error) { 618 if num == cty.PositiveInfinity || num == cty.NegativeInfinity { 619 return cty.NilVal, fmt.Errorf("can't truncate infinity to an integer") 620 } 621 return IntFunc.Call([]cty.Value{num}) 622} 623 624// Ceil returns the closest whole number greater than or equal to the given value. 625func Ceil(num cty.Value) (cty.Value, error) { 626 return CeilFunc.Call([]cty.Value{num}) 627} 628 629// Floor returns the closest whole number lesser than or equal to the given value. 630func Floor(num cty.Value) (cty.Value, error) { 631 return FloorFunc.Call([]cty.Value{num}) 632} 633 634// Log returns returns the logarithm of a given number in a given base. 635func Log(num, base cty.Value) (cty.Value, error) { 636 return LogFunc.Call([]cty.Value{num, base}) 637} 638 639// Pow returns the logarithm of a given number in a given base. 640func Pow(num, power cty.Value) (cty.Value, error) { 641 return PowFunc.Call([]cty.Value{num, power}) 642} 643 644// Signum determines the sign of a number, returning a number between -1 and 645// 1 to represent the sign. 646func Signum(num cty.Value) (cty.Value, error) { 647 return SignumFunc.Call([]cty.Value{num}) 648} 649 650// ParseInt parses a string argument and returns an integer of the specified base. 651func ParseInt(num cty.Value, base cty.Value) (cty.Value, error) { 652 return ParseIntFunc.Call([]cty.Value{num, base}) 653} 654