]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package stdlib |
2 | ||
3 | import ( | |
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 | ||
11 | var 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 | ||
107c1cdb ND |
122 | var 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 | ||
15c0b25d AP |
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. | |
197 | func Concat(seqs ...cty.Value) (cty.Value, error) { | |
198 | return ConcatFunc.Call(seqs) | |
199 | } | |
107c1cdb ND |
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. | |
216 | func Range(params ...cty.Value) (cty.Value, error) { | |
217 | return RangeFunc.Call(params) | |
218 | } |