spo_rhai/packages/map_basic.rs
1#![cfg(not(feature = "no_object"))]
2
3use crate::engine::OP_EQUALS;
4use crate::module::ModuleFlags;
5use crate::plugin::*;
6use crate::{def_package, Dynamic, ImmutableString, Map, NativeCallContext, RhaiResultOf, INT};
7#[cfg(feature = "no_std")]
8use std::prelude::v1::*;
9
10#[cfg(not(feature = "no_index"))]
11use crate::Array;
12
13def_package! {
14 /// Package of basic object map utilities.
15 pub BasicMapPackage(lib) {
16 lib.flags |= ModuleFlags::STANDARD_LIB;
17
18 combine_with_exported_module!(lib, "map", map_functions);
19 }
20}
21
22#[export_module]
23mod map_functions {
24 /// Return the number of properties in the object map.
25 #[rhai_fn(pure)]
26 pub fn len(map: &mut Map) -> INT {
27 map.len() as INT
28 }
29 /// Return true if the map is empty.
30 #[rhai_fn(pure)]
31 pub fn is_empty(map: &mut Map) -> bool {
32 map.len() == 0
33 }
34 /// Returns `true` if the object map contains a specified property.
35 ///
36 /// # Example
37 ///
38 /// ```rhai
39 /// let m = #{a: 1, b: 2, c: 3};
40 ///
41 /// print(m.contains("b")); // prints true
42 ///
43 /// print(m.contains("x")); // prints false
44 /// ```
45 pub fn contains(map: &mut Map, property: &str) -> bool {
46 map.contains_key(property)
47 }
48 /// Get the value of the `property` in the object map and return a copy.
49 ///
50 /// If `property` does not exist in the object map, `()` is returned.
51 ///
52 /// # Example
53 ///
54 /// ```rhai
55 /// let m = #{a: 1, b: 2, c: 3};
56 ///
57 /// print(m.get("b")); // prints 2
58 ///
59 /// print(m.get("x")); // prints empty (for '()')
60 /// ```
61 pub fn get(map: &mut Map, property: &str) -> Dynamic {
62 if map.is_empty() {
63 return Dynamic::UNIT;
64 }
65
66 map.get(property).cloned().unwrap_or(Dynamic::UNIT)
67 }
68 /// Set the value of the `property` in the object map to a new `value`.
69 ///
70 /// If `property` does not exist in the object map, it is added.
71 ///
72 /// # Example
73 ///
74 /// ```rhai
75 /// let m = #{a: 1, b: 2, c: 3};
76 ///
77 /// m.set("b", 42)'
78 ///
79 /// print(m); // prints "#{a: 1, b: 42, c: 3}"
80 ///
81 /// x.set("x", 0);
82 ///
83 /// print(m); // prints "#{a: 1, b: 42, c: 3, x: 0}"
84 /// ```
85 pub fn set(map: &mut Map, property: &str, value: Dynamic) {
86 match map.get_mut(property) {
87 Some(value_ref) => *value_ref = value,
88 None => {
89 map.insert(property.into(), value);
90 }
91 }
92 }
93 /// Clear the object map.
94 pub fn clear(map: &mut Map) {
95 if map.is_empty() {
96 return;
97 }
98
99 map.clear();
100 }
101 /// Remove any property of the specified `name` from the object map, returning its value.
102 ///
103 /// If the property does not exist, `()` is returned.
104 ///
105 /// # Example
106 ///
107 /// ```rhai
108 /// let m = #{a:1, b:2, c:3};
109 ///
110 /// let x = m.remove("b");
111 ///
112 /// print(x); // prints 2
113 ///
114 /// print(m); // prints "#{a:1, c:3}"
115 /// ```
116 pub fn remove(map: &mut Map, property: &str) -> Dynamic {
117 if map.is_empty() {
118 return Dynamic::UNIT;
119 }
120
121 #[cfg(not(feature = "indexmap"))]
122 {
123 map.remove(property).unwrap_or(Dynamic::UNIT)
124 }
125
126 #[cfg(feature = "indexmap")]
127 {
128 map.swap_remove(property).unwrap_or(Dynamic::UNIT)
129 }
130 }
131 /// Add all property values of another object map into the object map.
132 /// Existing property values of the same names are replaced.
133 ///
134 /// # Example
135 ///
136 /// ```rhai
137 /// let m = #{a:1, b:2, c:3};
138 /// let n = #{a: 42, d:0};
139 ///
140 /// m.mixin(n);
141 ///
142 /// print(m); // prints "#{a:42, b:2, c:3, d:0}"
143 /// ```
144 #[rhai_fn(name = "mixin", name = "+=")]
145 pub fn mixin(map: &mut Map, map2: Map) {
146 if map2.is_empty() {
147 return;
148 }
149
150 map.extend(map2);
151 }
152 /// Make a copy of the object map, add all property values of another object map
153 /// (existing property values of the same names are replaced), then returning it.
154 ///
155 /// # Example
156 ///
157 /// ```rhai
158 /// let m = #{a:1, b:2, c:3};
159 /// let n = #{a: 42, d:0};
160 ///
161 /// print(m + n); // prints "#{a:42, b:2, c:3, d:0}"
162 ///
163 /// print(m); // prints "#{a:1, b:2, c:3}"
164 /// ```
165 #[rhai_fn(name = "+")]
166 pub fn merge(map1: Map, map2: Map) -> Map {
167 if map2.is_empty() {
168 return map1;
169 }
170 if map1.is_empty() {
171 return map2;
172 }
173
174 let mut map1 = map1;
175 map1.extend(map2);
176 map1
177 }
178 /// Add all property values of another object map into the object map.
179 /// Only properties that do not originally exist in the object map are added.
180 ///
181 /// # Example
182 ///
183 /// ```rhai
184 /// let m = #{a:1, b:2, c:3};
185 /// let n = #{a: 42, d:0};
186 ///
187 /// m.fill_with(n);
188 ///
189 /// print(m); // prints "#{a:1, b:2, c:3, d:0}"
190 /// ```
191 pub fn fill_with(map: &mut Map, map2: Map) {
192 if map2.is_empty() {
193 return;
194 }
195 if map.is_empty() {
196 *map = map2;
197 return;
198 }
199
200 for (key, value) in map2 {
201 map.entry(key).or_insert(value);
202 }
203 }
204 /// Return `true` if two object maps are equal (i.e. all property values are equal).
205 ///
206 /// The operator `==` is used to compare property values and must be defined,
207 /// otherwise `false` is assumed.
208 ///
209 /// # Example
210 ///
211 /// ```rhai
212 /// let m1 = #{a:1, b:2, c:3};
213 /// let m2 = #{a:1, b:2, c:3};
214 /// let m3 = #{a:1, c:3};
215 ///
216 /// print(m1 == m2); // prints true
217 ///
218 /// print(m1 == m3); // prints false
219 /// ```
220 #[rhai_fn(name = "==", return_raw, pure)]
221 pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> {
222 if map1.len() != map2.len() {
223 return Ok(false);
224 }
225
226 if !map1.is_empty() {
227 let mut map2 = map2;
228
229 for (m1, v1) in map1 {
230 match map2.get_mut(m1) {
231 Some(v2) => {
232 let equals = ctx
233 .call_native_fn_raw(OP_EQUALS, true, &mut [v1, v2])?
234 .as_bool()
235 .unwrap_or(false);
236
237 if !equals {
238 return Ok(false);
239 }
240 }
241 _ => return Ok(false),
242 }
243 }
244 }
245
246 Ok(true)
247 }
248 /// Return `true` if two object maps are not equal (i.e. at least one property value is not equal).
249 ///
250 /// The operator `==` is used to compare property values and must be defined,
251 /// otherwise `false` is assumed.
252 ///
253 /// # Example
254 ///
255 /// ```rhai
256 /// let m1 = #{a:1, b:2, c:3};
257 /// let m2 = #{a:1, b:2, c:3};
258 /// let m3 = #{a:1, c:3};
259 ///
260 /// print(m1 != m2); // prints false
261 ///
262 /// print(m1 != m3); // prints true
263 /// ```
264 #[rhai_fn(name = "!=", return_raw, pure)]
265 pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> {
266 equals(ctx, map1, map2).map(|r| !r)
267 }
268
269 /// Return an array with all the property names in the object map.
270 ///
271 /// # Example
272 ///
273 /// ```rhai
274 /// let m = #{a:1, b:2, c:3};
275 ///
276 /// print(m.keys()); // prints ["a", "b", "c"]
277 /// ```
278 #[cfg(not(feature = "no_index"))]
279 #[rhai_fn(pure)]
280 pub fn keys(map: &mut Map) -> Array {
281 if map.is_empty() {
282 return Array::new();
283 }
284
285 map.keys().cloned().map(Into::into).collect()
286 }
287 /// Return an array with all the property values in the object map.
288 ///
289 /// # Example
290 ///
291 /// ```rhai
292 /// let m = #{a:1, b:2, c:3};
293 ///
294 /// print(m.values()); // prints "[1, 2, 3]""
295 /// ```
296 #[cfg(not(feature = "no_index"))]
297 #[rhai_fn(pure)]
298 pub fn values(map: &mut Map) -> Array {
299 if map.is_empty() {
300 return Array::new();
301 }
302
303 map.values().cloned().collect()
304 }
305 /// Return the JSON representation of the object map.
306 ///
307 /// # Data types
308 ///
309 /// Only the following data types should be kept inside the object map:
310 /// `INT`, `FLOAT`, `ImmutableString`, `char`, `bool`, `()`, `Array`, `Map`.
311 ///
312 /// # Errors
313 ///
314 /// Data types not supported by JSON serialize into formats that may
315 /// invalidate the result.
316 ///
317 /// # Example
318 ///
319 /// ```rhai
320 /// let m = #{a:1, b:2, c:3};
321 ///
322 /// print(m.to_json()); // prints {"a":1, "b":2, "c":3}
323 /// ```
324 pub fn to_json(map: &mut Map) -> String {
325 #[cfg(feature = "metadata")]
326 return serde_json::to_string(map).unwrap_or_else(|_| "ERROR".into());
327 #[cfg(not(feature = "metadata"))]
328 return crate::format_map_as_json(map);
329 }
330}