1package stdlib
2
3import (
4	"fmt"
5
6	"github.com/zclconf/go-cty/cty"
7	"github.com/zclconf/go-cty/cty/convert"
8	"github.com/zclconf/go-cty/cty/function"
9)
10
11var ConcatFunc = function.New(&function.Spec{
12	Params: []function.Parameter{},
13	VarParam: &function.Parameter{
14		Name: "seqs",
15		Type: cty.DynamicPseudoType,
16	},
17	Type: func(args []cty.Value) (ret cty.Type, err error) {
18		if len(args) == 0 {
19			return cty.NilType, fmt.Errorf("at least one argument is required")
20		}
21
22		if args[0].Type().IsListType() {
23			// Possibly we're going to return a list, if all of our other
24			// args are also lists and we can find a common element type.
25			tys := make([]cty.Type, len(args))
26			for i, val := range args {
27				ty := val.Type()
28				if !ty.IsListType() {
29					tys = nil
30					break
31				}
32				tys[i] = ty
33			}
34
35			if tys != nil {
36				commonType, _ := convert.UnifyUnsafe(tys)
37				if commonType != cty.NilType {
38					return commonType, nil
39				}
40			}
41		}
42
43		etys := make([]cty.Type, 0, len(args))
44		for i, val := range args {
45			ety := val.Type()
46			switch {
47			case ety.IsTupleType():
48				etys = append(etys, ety.TupleElementTypes()...)
49			case ety.IsListType():
50				if !val.IsKnown() {
51					// We need to know the list to count its elements to
52					// build our tuple type, so any concat of an unknown
53					// list can't be typed yet.
54					return cty.DynamicPseudoType, nil
55				}
56
57				l := val.LengthInt()
58				subEty := ety.ElementType()
59				for j := 0; j < l; j++ {
60					etys = append(etys, subEty)
61				}
62			default:
63				return cty.NilType, function.NewArgErrorf(
64					i, "all arguments must be lists or tuples; got %s",
65					ety.FriendlyName(),
66				)
67			}
68		}
69		return cty.Tuple(etys), nil
70	},
71	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
72		switch {
73		case retType.IsListType():
74			// If retType is a list type then we know that all of the
75			// given values will be lists and that they will either be of
76			// retType or of something we can convert to retType.
77			vals := make([]cty.Value, 0, len(args))
78			for i, list := range args {
79				list, err = convert.Convert(list, retType)
80				if err != nil {
81					// Conversion might fail because we used UnifyUnsafe
82					// to choose our return type.
83					return cty.NilVal, function.NewArgError(i, err)
84				}
85
86				it := list.ElementIterator()
87				for it.Next() {
88					_, v := it.Element()
89					vals = append(vals, v)
90				}
91			}
92			if len(vals) == 0 {
93				return cty.ListValEmpty(retType.ElementType()), nil
94			}
95
96			return cty.ListVal(vals), nil
97		case retType.IsTupleType():
98			// If retType is a tuple type then we could have a mixture of
99			// lists and tuples but we know they all have known values
100			// (because our params don't AllowUnknown) and we know that
101			// concatenating them all together will produce a tuple of
102			// retType because of the work we did in the Type function above.
103			vals := make([]cty.Value, 0, len(args))
104
105			for _, seq := range args {
106				// Both lists and tuples support ElementIterator, so this is easy.
107				it := seq.ElementIterator()
108				for it.Next() {
109					_, v := it.Element()
110					vals = append(vals, v)
111				}
112			}
113
114			return cty.TupleVal(vals), nil
115		default:
116			// should never happen if Type is working correctly above
117			panic("unsupported return type")
118		}
119	},
120})
121
122var RangeFunc = function.New(&function.Spec{
123	VarParam: &function.Parameter{
124		Name: "params",
125		Type: cty.Number,
126	},
127	Type: function.StaticReturnType(cty.List(cty.Number)),
128	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
129		var start, end, step cty.Value
130		switch len(args) {
131		case 1:
132			if args[0].LessThan(cty.Zero).True() {
133				start, end, step = cty.Zero, args[0], cty.NumberIntVal(-1)
134			} else {
135				start, end, step = cty.Zero, args[0], cty.NumberIntVal(1)
136			}
137		case 2:
138			if args[1].LessThan(args[0]).True() {
139				start, end, step = args[0], args[1], cty.NumberIntVal(-1)
140			} else {
141				start, end, step = args[0], args[1], cty.NumberIntVal(1)
142			}
143		case 3:
144			start, end, step = args[0], args[1], args[2]
145		default:
146			return cty.NilVal, fmt.Errorf("must have one, two, or three arguments")
147		}
148
149		var vals []cty.Value
150
151		if step == cty.Zero {
152			return cty.NilVal, function.NewArgErrorf(2, "step must not be zero")
153		}
154		down := step.LessThan(cty.Zero).True()
155
156		if down {
157			if end.GreaterThan(start).True() {
158				return cty.NilVal, function.NewArgErrorf(1, "end must be less than start when step is negative")
159			}
160		} else {
161			if end.LessThan(start).True() {
162				return cty.NilVal, function.NewArgErrorf(1, "end must be greater than start when step is positive")
163			}
164		}
165
166		num := start
167		for {
168			if down {
169				if num.LessThanOrEqualTo(end).True() {
170					break
171				}
172			} else {
173				if num.GreaterThanOrEqualTo(end).True() {
174					break
175				}
176			}
177			if len(vals) >= 1024 {
178				// Artificial limit to prevent bad arguments from consuming huge amounts of memory
179				return cty.NilVal, fmt.Errorf("more than 1024 values were generated; either decrease the difference between start and end or use a smaller step")
180			}
181			vals = append(vals, num)
182			num = num.Add(step)
183		}
184		if len(vals) == 0 {
185			return cty.ListValEmpty(cty.Number), nil
186		}
187		return cty.ListVal(vals), nil
188	},
189})
190
191// Concat takes one or more sequences (lists or tuples) and returns the single
192// sequence that results from concatenating them together in order.
193//
194// If all of the given sequences are lists of the same element type then the
195// result is a list of that type. Otherwise, the result is a of a tuple type
196// constructed from the given sequence types.
197func Concat(seqs ...cty.Value) (cty.Value, error) {
198	return ConcatFunc.Call(seqs)
199}
200
201// Range creates a list of numbers by starting from the given starting value,
202// then adding the given step value until the result is greater than or
203// equal to the given stopping value. Each intermediate result becomes an
204// element in the resulting list.
205//
206// When all three parameters are set, the order is (start, end, step). If
207// only two parameters are set, they are the start and end respectively and
208// step defaults to 1. If only one argument is set, it gives the end value
209// with start defaulting to 0 and step defaulting to 1.
210//
211// Because the resulting list must be fully buffered in memory, there is an
212// artificial cap of 1024 elements, after which this function will return
213// an error to avoid consuming unbounded amounts of memory. The Range function
214// is primarily intended for creating small lists of indices to iterate over,
215// so there should be no reason to generate huge lists with it.
216func Range(params ...cty.Value) (cty.Value, error) {
217	return RangeFunc.Call(params)
218}
219