Skip to main content

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}