| package stdlib |
| |
| import ( |
| "fmt" |
| |
| "github.com/zclconf/go-cty/cty" |
| "github.com/zclconf/go-cty/cty/convert" |
| "github.com/zclconf/go-cty/cty/function" |
| ) |
| |
| var ConcatFunc = function.New(&function.Spec{ |
| Params: []function.Parameter{}, |
| VarParam: &function.Parameter{ |
| Name: "seqs", |
| Type: cty.DynamicPseudoType, |
| }, |
| Type: func(args []cty.Value) (ret cty.Type, err error) { |
| if len(args) == 0 { |
| return cty.NilType, fmt.Errorf("at least one argument is required") |
| } |
| |
| if args[0].Type().IsListType() { |
| // Possibly we're going to return a list, if all of our other |
| // args are also lists and we can find a common element type. |
| tys := make([]cty.Type, len(args)) |
| for i, val := range args { |
| ty := val.Type() |
| if !ty.IsListType() { |
| tys = nil |
| break |
| } |
| tys[i] = ty |
| } |
| |
| if tys != nil { |
| commonType, _ := convert.UnifyUnsafe(tys) |
| if commonType != cty.NilType { |
| return commonType, nil |
| } |
| } |
| } |
| |
| etys := make([]cty.Type, 0, len(args)) |
| for i, val := range args { |
| ety := val.Type() |
| switch { |
| case ety.IsTupleType(): |
| etys = append(etys, ety.TupleElementTypes()...) |
| case ety.IsListType(): |
| if !val.IsKnown() { |
| // We need to know the list to count its elements to |
| // build our tuple type, so any concat of an unknown |
| // list can't be typed yet. |
| return cty.DynamicPseudoType, nil |
| } |
| |
| l := val.LengthInt() |
| subEty := ety.ElementType() |
| for j := 0; j < l; j++ { |
| etys = append(etys, subEty) |
| } |
| default: |
| return cty.NilType, function.NewArgErrorf( |
| i, "all arguments must be lists or tuples; got %s", |
| ety.FriendlyName(), |
| ) |
| } |
| } |
| return cty.Tuple(etys), nil |
| }, |
| Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { |
| switch { |
| case retType.IsListType(): |
| // If retType is a list type then we know that all of the |
| // given values will be lists and that they will either be of |
| // retType or of something we can convert to retType. |
| vals := make([]cty.Value, 0, len(args)) |
| for i, list := range args { |
| list, err = convert.Convert(list, retType) |
| if err != nil { |
| // Conversion might fail because we used UnifyUnsafe |
| // to choose our return type. |
| return cty.NilVal, function.NewArgError(i, err) |
| } |
| |
| it := list.ElementIterator() |
| for it.Next() { |
| _, v := it.Element() |
| vals = append(vals, v) |
| } |
| } |
| if len(vals) == 0 { |
| return cty.ListValEmpty(retType.ElementType()), nil |
| } |
| |
| return cty.ListVal(vals), nil |
| case retType.IsTupleType(): |
| // If retType is a tuple type then we could have a mixture of |
| // lists and tuples but we know they all have known values |
| // (because our params don't AllowUnknown) and we know that |
| // concatenating them all together will produce a tuple of |
| // retType because of the work we did in the Type function above. |
| vals := make([]cty.Value, 0, len(args)) |
| |
| for _, seq := range args { |
| // Both lists and tuples support ElementIterator, so this is easy. |
| it := seq.ElementIterator() |
| for it.Next() { |
| _, v := it.Element() |
| vals = append(vals, v) |
| } |
| } |
| |
| return cty.TupleVal(vals), nil |
| default: |
| // should never happen if Type is working correctly above |
| panic("unsupported return type") |
| } |
| }, |
| }) |
| |
| var RangeFunc = function.New(&function.Spec{ |
| VarParam: &function.Parameter{ |
| Name: "params", |
| Type: cty.Number, |
| }, |
| Type: function.StaticReturnType(cty.List(cty.Number)), |
| Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { |
| var start, end, step cty.Value |
| switch len(args) { |
| case 1: |
| if args[0].LessThan(cty.Zero).True() { |
| start, end, step = cty.Zero, args[0], cty.NumberIntVal(-1) |
| } else { |
| start, end, step = cty.Zero, args[0], cty.NumberIntVal(1) |
| } |
| case 2: |
| if args[1].LessThan(args[0]).True() { |
| start, end, step = args[0], args[1], cty.NumberIntVal(-1) |
| } else { |
| start, end, step = args[0], args[1], cty.NumberIntVal(1) |
| } |
| case 3: |
| start, end, step = args[0], args[1], args[2] |
| default: |
| return cty.NilVal, fmt.Errorf("must have one, two, or three arguments") |
| } |
| |
| var vals []cty.Value |
| |
| if step == cty.Zero { |
| return cty.NilVal, function.NewArgErrorf(2, "step must not be zero") |
| } |
| down := step.LessThan(cty.Zero).True() |
| |
| if down { |
| if end.GreaterThan(start).True() { |
| return cty.NilVal, function.NewArgErrorf(1, "end must be less than start when step is negative") |
| } |
| } else { |
| if end.LessThan(start).True() { |
| return cty.NilVal, function.NewArgErrorf(1, "end must be greater than start when step is positive") |
| } |
| } |
| |
| num := start |
| for { |
| if down { |
| if num.LessThanOrEqualTo(end).True() { |
| break |
| } |
| } else { |
| if num.GreaterThanOrEqualTo(end).True() { |
| break |
| } |
| } |
| if len(vals) >= 1024 { |
| // Artificial limit to prevent bad arguments from consuming huge amounts of memory |
| return cty.NilVal, fmt.Errorf("more than 1024 values were generated; either decrease the difference between start and end or use a smaller step") |
| } |
| vals = append(vals, num) |
| num = num.Add(step) |
| } |
| if len(vals) == 0 { |
| return cty.ListValEmpty(cty.Number), nil |
| } |
| return cty.ListVal(vals), nil |
| }, |
| }) |
| |
| // Concat takes one or more sequences (lists or tuples) and returns the single |
| // sequence that results from concatenating them together in order. |
| // |
| // If all of the given sequences are lists of the same element type then the |
| // result is a list of that type. Otherwise, the result is a of a tuple type |
| // constructed from the given sequence types. |
| func Concat(seqs ...cty.Value) (cty.Value, error) { |
| return ConcatFunc.Call(seqs) |
| } |
| |
| // Range creates a list of numbers by starting from the given starting value, |
| // then adding the given step value until the result is greater than or |
| // equal to the given stopping value. Each intermediate result becomes an |
| // element in the resulting list. |
| // |
| // When all three parameters are set, the order is (start, end, step). If |
| // only two parameters are set, they are the start and end respectively and |
| // step defaults to 1. If only one argument is set, it gives the end value |
| // with start defaulting to 0 and step defaulting to 1. |
| // |
| // Because the resulting list must be fully buffered in memory, there is an |
| // artificial cap of 1024 elements, after which this function will return |
| // an error to avoid consuming unbounded amounts of memory. The Range function |
| // is primarily intended for creating small lists of indices to iterate over, |
| // so there should be no reason to generate huge lists with it. |
| func Range(params ...cty.Value) (cty.Value, error) { |
| return RangeFunc.Call(params) |
| } |