]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package cty |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "math/big" | |
6 | "reflect" | |
7 | ||
8 | "golang.org/x/text/unicode/norm" | |
9 | ||
10 | "github.com/zclconf/go-cty/cty/set" | |
11 | ) | |
12 | ||
13 | // BoolVal returns a Value of type Number whose internal value is the given | |
14 | // bool. | |
15 | func BoolVal(v bool) Value { | |
16 | return Value{ | |
17 | ty: Bool, | |
18 | v: v, | |
19 | } | |
20 | } | |
21 | ||
22 | // NumberVal returns a Value of type Number whose internal value is the given | |
23 | // big.Float. The returned value becomes the owner of the big.Float object, | |
24 | // and so it's forbidden for the caller to mutate the object after it's | |
25 | // wrapped in this way. | |
26 | func NumberVal(v *big.Float) Value { | |
27 | return Value{ | |
28 | ty: Number, | |
29 | v: v, | |
30 | } | |
31 | } | |
32 | ||
107c1cdb ND |
33 | // ParseNumberVal returns a Value of type number produced by parsing the given |
34 | // string as a decimal real number. To ensure that two identical strings will | |
35 | // always produce an equal number, always use this function to derive a number | |
36 | // from a string; it will ensure that the precision and rounding mode for the | |
37 | // internal big decimal is configured in a consistent way. | |
38 | // | |
39 | // If the given string cannot be parsed as a number, the returned error has | |
40 | // the message "a number is required", making it suitable to return to an | |
41 | // end-user to signal a type conversion error. | |
42 | // | |
43 | // If the given string contains a number that becomes a recurring fraction | |
44 | // when expressed in binary then it will be truncated to have a 512-bit | |
45 | // mantissa. Note that this is a higher precision than that of a float64, | |
46 | // so coverting the same decimal number first to float64 and then calling | |
47 | // NumberFloatVal will not produce an equal result; the conversion first | |
48 | // to float64 will round the mantissa to fewer than 512 bits. | |
49 | func ParseNumberVal(s string) (Value, error) { | |
50 | // Base 10, precision 512, and rounding to nearest even is the standard | |
51 | // way to handle numbers arriving as strings. | |
52 | f, _, err := big.ParseFloat(s, 10, 512, big.ToNearestEven) | |
53 | if err != nil { | |
54 | return NilVal, fmt.Errorf("a number is required") | |
55 | } | |
56 | return NumberVal(f), nil | |
57 | } | |
58 | ||
59 | // MustParseNumberVal is like ParseNumberVal but it will panic in case of any | |
60 | // error. It can be used during initialization or any other situation where | |
61 | // the given string is a constant or otherwise known to be correct by the | |
62 | // caller. | |
63 | func MustParseNumberVal(s string) Value { | |
64 | ret, err := ParseNumberVal(s) | |
65 | if err != nil { | |
66 | panic(err) | |
67 | } | |
68 | return ret | |
69 | } | |
70 | ||
15c0b25d AP |
71 | // NumberIntVal returns a Value of type Number whose internal value is equal |
72 | // to the given integer. | |
73 | func NumberIntVal(v int64) Value { | |
74 | return NumberVal(new(big.Float).SetInt64(v)) | |
75 | } | |
76 | ||
77 | // NumberUIntVal returns a Value of type Number whose internal value is equal | |
78 | // to the given unsigned integer. | |
79 | func NumberUIntVal(v uint64) Value { | |
80 | return NumberVal(new(big.Float).SetUint64(v)) | |
81 | } | |
82 | ||
83 | // NumberFloatVal returns a Value of type Number whose internal value is | |
84 | // equal to the given float. | |
85 | func NumberFloatVal(v float64) Value { | |
86 | return NumberVal(new(big.Float).SetFloat64(v)) | |
87 | } | |
88 | ||
89 | // StringVal returns a Value of type String whose internal value is the | |
90 | // given string. | |
91 | // | |
92 | // Strings must be UTF-8 encoded sequences of valid unicode codepoints, and | |
93 | // they are NFC-normalized on entry into the world of cty values. | |
94 | // | |
95 | // If the given string is not valid UTF-8 then behavior of string operations | |
96 | // is undefined. | |
97 | func StringVal(v string) Value { | |
98 | return Value{ | |
99 | ty: String, | |
100 | v: NormalizeString(v), | |
101 | } | |
102 | } | |
103 | ||
104 | // NormalizeString applies the same normalization that cty applies when | |
105 | // constructing string values. | |
106 | // | |
107 | // A return value from this function can be meaningfully compared byte-for-byte | |
108 | // with a Value.AsString result. | |
109 | func NormalizeString(s string) string { | |
110 | return norm.NFC.String(s) | |
111 | } | |
112 | ||
113 | // ObjectVal returns a Value of an object type whose structure is defined | |
114 | // by the key names and value types in the given map. | |
115 | func ObjectVal(attrs map[string]Value) Value { | |
116 | attrTypes := make(map[string]Type, len(attrs)) | |
117 | attrVals := make(map[string]interface{}, len(attrs)) | |
118 | ||
119 | for attr, val := range attrs { | |
120 | attr = NormalizeString(attr) | |
121 | attrTypes[attr] = val.ty | |
122 | attrVals[attr] = val.v | |
123 | } | |
124 | ||
125 | return Value{ | |
126 | ty: Object(attrTypes), | |
127 | v: attrVals, | |
128 | } | |
129 | } | |
130 | ||
131 | // TupleVal returns a Value of a tuple type whose element types are | |
132 | // defined by the value types in the given slice. | |
133 | func TupleVal(elems []Value) Value { | |
134 | elemTypes := make([]Type, len(elems)) | |
135 | elemVals := make([]interface{}, len(elems)) | |
136 | ||
137 | for i, val := range elems { | |
138 | elemTypes[i] = val.ty | |
139 | elemVals[i] = val.v | |
140 | } | |
141 | ||
142 | return Value{ | |
143 | ty: Tuple(elemTypes), | |
144 | v: elemVals, | |
145 | } | |
146 | } | |
147 | ||
148 | // ListVal returns a Value of list type whose element type is defined by | |
149 | // the types of the given values, which must be homogenous. | |
150 | // | |
151 | // If the types are not all consistent (aside from elements that are of the | |
152 | // dynamic pseudo-type) then this function will panic. It will panic also | |
153 | // if the given list is empty, since then the element type cannot be inferred. | |
154 | // (See also ListValEmpty.) | |
155 | func ListVal(vals []Value) Value { | |
156 | if len(vals) == 0 { | |
157 | panic("must not call ListVal with empty slice") | |
158 | } | |
159 | elementType := DynamicPseudoType | |
160 | rawList := make([]interface{}, len(vals)) | |
161 | ||
162 | for i, val := range vals { | |
163 | if elementType == DynamicPseudoType { | |
164 | elementType = val.ty | |
165 | } else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) { | |
166 | panic(fmt.Errorf( | |
167 | "inconsistent list element types (%#v then %#v)", | |
168 | elementType, val.ty, | |
169 | )) | |
170 | } | |
171 | ||
172 | rawList[i] = val.v | |
173 | } | |
174 | ||
175 | return Value{ | |
176 | ty: List(elementType), | |
177 | v: rawList, | |
178 | } | |
179 | } | |
180 | ||
181 | // ListValEmpty returns an empty list of the given element type. | |
182 | func ListValEmpty(element Type) Value { | |
183 | return Value{ | |
184 | ty: List(element), | |
185 | v: []interface{}{}, | |
186 | } | |
187 | } | |
188 | ||
189 | // MapVal returns a Value of a map type whose element type is defined by | |
190 | // the types of the given values, which must be homogenous. | |
191 | // | |
192 | // If the types are not all consistent (aside from elements that are of the | |
193 | // dynamic pseudo-type) then this function will panic. It will panic also | |
194 | // if the given map is empty, since then the element type cannot be inferred. | |
195 | // (See also MapValEmpty.) | |
196 | func MapVal(vals map[string]Value) Value { | |
197 | if len(vals) == 0 { | |
198 | panic("must not call MapVal with empty map") | |
199 | } | |
200 | elementType := DynamicPseudoType | |
201 | rawMap := make(map[string]interface{}, len(vals)) | |
202 | ||
203 | for key, val := range vals { | |
204 | if elementType == DynamicPseudoType { | |
205 | elementType = val.ty | |
206 | } else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) { | |
207 | panic(fmt.Errorf( | |
208 | "inconsistent map element types (%#v then %#v)", | |
209 | elementType, val.ty, | |
210 | )) | |
211 | } | |
212 | ||
213 | rawMap[NormalizeString(key)] = val.v | |
214 | } | |
215 | ||
216 | return Value{ | |
217 | ty: Map(elementType), | |
218 | v: rawMap, | |
219 | } | |
220 | } | |
221 | ||
222 | // MapValEmpty returns an empty map of the given element type. | |
223 | func MapValEmpty(element Type) Value { | |
224 | return Value{ | |
225 | ty: Map(element), | |
226 | v: map[string]interface{}{}, | |
227 | } | |
228 | } | |
229 | ||
230 | // SetVal returns a Value of set type whose element type is defined by | |
231 | // the types of the given values, which must be homogenous. | |
232 | // | |
233 | // If the types are not all consistent (aside from elements that are of the | |
234 | // dynamic pseudo-type) then this function will panic. It will panic also | |
235 | // if the given list is empty, since then the element type cannot be inferred. | |
236 | // (See also SetValEmpty.) | |
237 | func SetVal(vals []Value) Value { | |
238 | if len(vals) == 0 { | |
239 | panic("must not call SetVal with empty slice") | |
240 | } | |
241 | elementType := DynamicPseudoType | |
242 | rawList := make([]interface{}, len(vals)) | |
243 | ||
244 | for i, val := range vals { | |
245 | if elementType == DynamicPseudoType { | |
246 | elementType = val.ty | |
247 | } else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) { | |
248 | panic(fmt.Errorf( | |
249 | "inconsistent set element types (%#v then %#v)", | |
250 | elementType, val.ty, | |
251 | )) | |
252 | } | |
253 | ||
254 | rawList[i] = val.v | |
255 | } | |
256 | ||
257 | rawVal := set.NewSetFromSlice(setRules{elementType}, rawList) | |
258 | ||
259 | return Value{ | |
260 | ty: Set(elementType), | |
261 | v: rawVal, | |
262 | } | |
263 | } | |
264 | ||
265 | // SetValFromValueSet returns a Value of set type based on an already-constructed | |
266 | // ValueSet. | |
267 | // | |
268 | // The element type of the returned value is the element type of the given | |
269 | // set. | |
270 | func SetValFromValueSet(s ValueSet) Value { | |
271 | ety := s.ElementType() | |
272 | rawVal := s.s.Copy() // copy so caller can't mutate what we wrap | |
273 | ||
274 | return Value{ | |
275 | ty: Set(ety), | |
276 | v: rawVal, | |
277 | } | |
278 | } | |
279 | ||
280 | // SetValEmpty returns an empty set of the given element type. | |
281 | func SetValEmpty(element Type) Value { | |
282 | return Value{ | |
283 | ty: Set(element), | |
284 | v: set.NewSet(setRules{element}), | |
285 | } | |
286 | } | |
287 | ||
288 | // CapsuleVal creates a value of the given capsule type using the given | |
289 | // wrapVal, which must be a pointer to a value of the capsule type's native | |
290 | // type. | |
291 | // | |
292 | // This function will panic if the given type is not a capsule type, if | |
293 | // the given wrapVal is not compatible with the given capsule type, or if | |
294 | // wrapVal is not a pointer. | |
295 | func CapsuleVal(ty Type, wrapVal interface{}) Value { | |
296 | if !ty.IsCapsuleType() { | |
297 | panic("not a capsule type") | |
298 | } | |
299 | ||
300 | wv := reflect.ValueOf(wrapVal) | |
301 | if wv.Kind() != reflect.Ptr { | |
302 | panic("wrapVal is not a pointer") | |
303 | } | |
304 | ||
305 | it := ty.typeImpl.(*capsuleType).GoType | |
306 | if !wv.Type().Elem().AssignableTo(it) { | |
307 | panic("wrapVal target is not compatible with the given capsule type") | |
308 | } | |
309 | ||
310 | return Value{ | |
311 | ty: ty, | |
312 | v: wrapVal, | |
313 | } | |
314 | } |