quad_compat_rhai/packages/
array_basic.rs

1#![cfg(not(feature = "no_index"))]
2#![allow(non_snake_case)]
3
4use crate::engine::OP_EQUALS;
5use crate::plugin::*;
6use crate::{
7    def_package, Array, Dynamic, EvalAltResult, ExclusiveRange, FnPtr, InclusiveRange,
8    NativeCallContext, Position, INT,
9};
10#[cfg(feature = "no_std")]
11use std::prelude::v1::*;
12use std::{any::TypeId, cmp::Ordering, mem};
13
14def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
15    lib.standard = true;
16
17    combine_with_exported_module!(lib, "array", array_functions);
18
19    // Register array iterator
20    lib.set_iterable::<Array>();
21});
22
23#[export_module]
24mod array_functions {
25    #[rhai_fn(name = "len", get = "len", pure)]
26    pub fn len(array: &mut Array) -> INT {
27        array.len() as INT
28    }
29    #[rhai_fn(name = "push", name = "+=")]
30    pub fn push(array: &mut Array, item: Dynamic) {
31        array.push(item);
32    }
33    #[rhai_fn(name = "append", name = "+=")]
34    pub fn append(array: &mut Array, y: Array) {
35        if !y.is_empty() {
36            if array.is_empty() {
37                *array = y;
38            } else {
39                array.extend(y);
40            }
41        }
42    }
43    #[rhai_fn(name = "+")]
44    pub fn concat(mut array: Array, y: Array) -> Array {
45        if !y.is_empty() {
46            if array.is_empty() {
47                array = y;
48            } else {
49                array.extend(y);
50            }
51        }
52        array
53    }
54    pub fn insert(array: &mut Array, position: INT, item: Dynamic) {
55        if array.is_empty() {
56            array.push(item);
57        } else if position < 0 {
58            if let Some(n) = position.checked_abs() {
59                if n as usize > array.len() {
60                    array.insert(0, item);
61                } else {
62                    array.insert(array.len() - n as usize, item);
63                }
64            } else {
65                array.insert(0, item);
66            }
67        } else if (position as usize) >= array.len() {
68            array.push(item);
69        } else {
70            array.insert(position as usize, item);
71        }
72    }
73    #[rhai_fn(return_raw)]
74    pub fn pad(
75        ctx: NativeCallContext,
76        array: &mut Array,
77        len: INT,
78        item: Dynamic,
79    ) -> Result<(), Box<EvalAltResult>> {
80        if len <= 0 {
81            return Ok(());
82        }
83
84        let _ctx = ctx;
85
86        // Check if array will be over max size limit
87        #[cfg(not(feature = "unchecked"))]
88        if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() {
89            return Err(EvalAltResult::ErrorDataTooLarge(
90                "Size of array".to_string(),
91                Position::NONE,
92            )
93            .into());
94        }
95
96        if len as usize > array.len() {
97            array.resize(len as usize, item);
98        }
99
100        Ok(())
101    }
102    pub fn pop(array: &mut Array) -> Dynamic {
103        if array.is_empty() {
104            Dynamic::UNIT
105        } else {
106            array.pop().unwrap_or_else(|| Dynamic::UNIT)
107        }
108    }
109    pub fn shift(array: &mut Array) -> Dynamic {
110        if array.is_empty() {
111            Dynamic::UNIT
112        } else {
113            array.remove(0)
114        }
115    }
116    pub fn remove(array: &mut Array, len: INT) -> Dynamic {
117        if len < 0 || (len as usize) >= array.len() {
118            Dynamic::UNIT
119        } else {
120            array.remove(len as usize)
121        }
122    }
123    pub fn clear(array: &mut Array) {
124        if !array.is_empty() {
125            array.clear();
126        }
127    }
128    pub fn truncate(array: &mut Array, len: INT) {
129        if !array.is_empty() {
130            if len >= 0 {
131                array.truncate(len as usize);
132            } else {
133                array.clear();
134            }
135        }
136    }
137    pub fn chop(array: &mut Array, len: INT) {
138        if !array.is_empty() && len as usize >= array.len() {
139            if len >= 0 {
140                array.drain(0..array.len() - len as usize);
141            } else {
142                array.clear();
143            }
144        }
145    }
146    pub fn reverse(array: &mut Array) {
147        if !array.is_empty() {
148            array.reverse();
149        }
150    }
151    #[rhai_fn(name = "splice")]
152    pub fn splice_range(array: &mut Array, range: ExclusiveRange, replace: Array) {
153        let start = INT::max(range.start, 0);
154        let end = INT::max(range.end, start);
155        splice(array, start, end - start, replace)
156    }
157    #[rhai_fn(name = "splice")]
158    pub fn splice_inclusive_range(array: &mut Array, range: InclusiveRange, replace: Array) {
159        let start = INT::max(*range.start(), 0);
160        let end = INT::max(*range.end(), start);
161        splice(array, start, end - start + 1, replace)
162    }
163    pub fn splice(array: &mut Array, start: INT, len: INT, replace: Array) {
164        if array.is_empty() {
165            *array = replace;
166            return;
167        }
168
169        let start = if start < 0 {
170            let arr_len = array.len();
171            start
172                .checked_abs()
173                .map_or(0, |n| arr_len - (n as usize).min(arr_len))
174        } else if start as usize >= array.len() {
175            array.extend(replace.into_iter());
176            return;
177        } else {
178            start as usize
179        };
180
181        let len = if len < 0 {
182            0
183        } else if len as usize > array.len() - start {
184            array.len() - start
185        } else {
186            len as usize
187        };
188
189        array.splice(start..start + len, replace.into_iter());
190    }
191    #[rhai_fn(name = "extract")]
192    pub fn extract_range(array: &mut Array, range: ExclusiveRange) -> Array {
193        let start = INT::max(range.start, 0);
194        let end = INT::max(range.end, start);
195        extract(array, start, end - start)
196    }
197    #[rhai_fn(name = "extract")]
198    pub fn extract_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array {
199        let start = INT::max(*range.start(), 0);
200        let end = INT::max(*range.end(), start);
201        extract(array, start, end - start + 1)
202    }
203    pub fn extract(array: &mut Array, start: INT, len: INT) -> Array {
204        if array.is_empty() || len <= 0 {
205            return Array::new();
206        }
207
208        let start = if start < 0 {
209            let arr_len = array.len();
210            start
211                .checked_abs()
212                .map_or(0, |n| arr_len - (n as usize).min(arr_len))
213        } else if start as usize >= array.len() {
214            return Array::new();
215        } else {
216            start as usize
217        };
218
219        let len = if len <= 0 {
220            0
221        } else if len as usize > array.len() - start {
222            array.len() - start
223        } else {
224            len as usize
225        };
226
227        if len == 0 {
228            Array::new()
229        } else {
230            array[start..start + len].to_vec()
231        }
232    }
233    #[rhai_fn(name = "extract")]
234    pub fn extract_tail(array: &mut Array, start: INT) -> Array {
235        extract(array, start, INT::MAX)
236    }
237    #[rhai_fn(name = "split")]
238    pub fn split_at(array: &mut Array, start: INT) -> Array {
239        if array.is_empty() {
240            Array::new()
241        } else if start < 0 {
242            if let Some(n) = start.checked_abs() {
243                if n as usize > array.len() {
244                    mem::take(array)
245                } else {
246                    let mut result = Array::new();
247                    result.extend(array.drain(array.len() - n as usize..));
248                    result
249                }
250            } else {
251                mem::take(array)
252            }
253        } else if start as usize >= array.len() {
254            Array::new()
255        } else {
256            let mut result = Array::new();
257            result.extend(array.drain(start as usize..));
258            result
259        }
260    }
261    #[rhai_fn(return_raw, pure)]
262    pub fn map(
263        ctx: NativeCallContext,
264        array: &mut Array,
265        mapper: FnPtr,
266    ) -> Result<Array, Box<EvalAltResult>> {
267        if array.is_empty() {
268            return Ok(array.clone());
269        }
270
271        let mut ar = Array::with_capacity(array.len());
272
273        for (i, item) in array.iter().enumerate() {
274            ar.push(
275                mapper
276                    .call_raw(&ctx, None, [item.clone()])
277                    .or_else(|err| match *err {
278                        EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
279                            if fn_sig.starts_with(mapper.fn_name()) =>
280                        {
281                            mapper.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
282                        }
283                        _ => Err(err),
284                    })
285                    .map_err(|err| {
286                        Box::new(EvalAltResult::ErrorInFunctionCall(
287                            "map".to_string(),
288                            ctx.source().unwrap_or("").to_string(),
289                            err,
290                            Position::NONE,
291                        ))
292                    })?,
293            );
294        }
295
296        Ok(ar)
297    }
298    #[rhai_fn(name = "map", return_raw, pure)]
299    pub fn map_with_fn_name(
300        ctx: NativeCallContext,
301        array: &mut Array,
302        mapper: &str,
303    ) -> Result<Array, Box<EvalAltResult>> {
304        map(ctx, array, FnPtr::new(mapper)?)
305    }
306
307    #[rhai_fn(return_raw, pure)]
308    pub fn filter(
309        ctx: NativeCallContext,
310        array: &mut Array,
311        filter: FnPtr,
312    ) -> Result<Array, Box<EvalAltResult>> {
313        if array.is_empty() {
314            return Ok(array.clone());
315        }
316
317        let mut ar = Array::new();
318
319        for (i, item) in array.iter().enumerate() {
320            if filter
321                .call_raw(&ctx, None, [item.clone()])
322                .or_else(|err| match *err {
323                    EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
324                        if fn_sig.starts_with(filter.fn_name()) =>
325                    {
326                        filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
327                    }
328                    _ => Err(err),
329                })
330                .map_err(|err| {
331                    Box::new(EvalAltResult::ErrorInFunctionCall(
332                        "filter".to_string(),
333                        ctx.source().unwrap_or("").to_string(),
334                        err,
335                        Position::NONE,
336                    ))
337                })?
338                .as_bool()
339                .unwrap_or(false)
340            {
341                ar.push(item.clone());
342            }
343        }
344
345        Ok(ar)
346    }
347    #[rhai_fn(name = "filter", return_raw, pure)]
348    pub fn filter_with_fn_name(
349        ctx: NativeCallContext,
350        array: &mut Array,
351        filter_func: &str,
352    ) -> Result<Array, Box<EvalAltResult>> {
353        filter(ctx, array, FnPtr::new(filter_func)?)
354    }
355    #[rhai_fn(return_raw, pure)]
356    pub fn contains(
357        ctx: NativeCallContext,
358        array: &mut Array,
359        value: Dynamic,
360    ) -> Result<bool, Box<EvalAltResult>> {
361        if array.is_empty() {
362            return Ok(false);
363        }
364
365        for item in array.iter_mut() {
366            if ctx
367                .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()])
368                .or_else(|err| match *err {
369                    EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
370                        if fn_sig.starts_with(OP_EQUALS) =>
371                    {
372                        if item.type_id() == value.type_id() {
373                            // No default when comparing same type
374                            Err(err)
375                        } else {
376                            Ok(Dynamic::FALSE)
377                        }
378                    }
379                    _ => Err(err),
380                })?
381                .as_bool()
382                .unwrap_or(false)
383            {
384                return Ok(true);
385            }
386        }
387
388        Ok(false)
389    }
390    #[rhai_fn(return_raw, pure)]
391    pub fn index_of(
392        ctx: NativeCallContext,
393        array: &mut Array,
394        value: Dynamic,
395    ) -> Result<INT, Box<EvalAltResult>> {
396        if array.is_empty() {
397            Ok(-1)
398        } else {
399            index_of_starting_from(ctx, array, value, 0)
400        }
401    }
402    #[rhai_fn(name = "index_of", return_raw, pure)]
403    pub fn index_of_starting_from(
404        ctx: NativeCallContext,
405        array: &mut Array,
406        value: Dynamic,
407        start: INT,
408    ) -> Result<INT, Box<EvalAltResult>> {
409        if array.is_empty() {
410            return Ok(-1);
411        }
412
413        let start = if start < 0 {
414            let arr_len = array.len();
415            start
416                .checked_abs()
417                .map_or(0, |n| arr_len - (n as usize).min(arr_len))
418        } else if start as usize >= array.len() {
419            return Ok(-1);
420        } else {
421            start as usize
422        };
423
424        for (i, item) in array.iter_mut().enumerate().skip(start) {
425            if ctx
426                .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()])
427                .or_else(|err| match *err {
428                    EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
429                        if fn_sig.starts_with(OP_EQUALS) =>
430                    {
431                        if item.type_id() == value.type_id() {
432                            // No default when comparing same type
433                            Err(err)
434                        } else {
435                            Ok(Dynamic::FALSE)
436                        }
437                    }
438                    _ => Err(err),
439                })?
440                .as_bool()
441                .unwrap_or(false)
442            {
443                return Ok(i as INT);
444            }
445        }
446
447        Ok(-1 as INT)
448    }
449    #[rhai_fn(name = "index_of", return_raw, pure)]
450    pub fn index_of_with_fn_name(
451        ctx: NativeCallContext,
452        array: &mut Array,
453        filter: &str,
454    ) -> Result<INT, Box<EvalAltResult>> {
455        index_of_filter(ctx, array, FnPtr::new(filter)?)
456    }
457    #[rhai_fn(name = "index_of", return_raw, pure)]
458    pub fn index_of_filter(
459        ctx: NativeCallContext,
460        array: &mut Array,
461        filter: FnPtr,
462    ) -> Result<INT, Box<EvalAltResult>> {
463        if array.is_empty() {
464            Ok(-1)
465        } else {
466            index_of_filter_starting_from(ctx, array, filter, 0)
467        }
468    }
469    #[rhai_fn(name = "index_of", return_raw, pure)]
470    pub fn index_of_filter_starting_from(
471        ctx: NativeCallContext,
472        array: &mut Array,
473        filter: FnPtr,
474        start: INT,
475    ) -> Result<INT, Box<EvalAltResult>> {
476        if array.is_empty() {
477            return Ok(-1);
478        }
479
480        let start = if start < 0 {
481            let arr_len = array.len();
482            start
483                .checked_abs()
484                .map_or(0, |n| arr_len - (n as usize).min(arr_len))
485        } else if start as usize >= array.len() {
486            return Ok(-1);
487        } else {
488            start as usize
489        };
490
491        for (i, item) in array.iter().enumerate().skip(start) {
492            if filter
493                .call_raw(&ctx, None, [item.clone()])
494                .or_else(|err| match *err {
495                    EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
496                        if fn_sig.starts_with(filter.fn_name()) =>
497                    {
498                        filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
499                    }
500                    _ => Err(err),
501                })
502                .map_err(|err| {
503                    Box::new(EvalAltResult::ErrorInFunctionCall(
504                        "index_of".to_string(),
505                        ctx.source().unwrap_or("").to_string(),
506                        err,
507                        Position::NONE,
508                    ))
509                })?
510                .as_bool()
511                .unwrap_or(false)
512            {
513                return Ok(i as INT);
514            }
515        }
516
517        Ok(-1 as INT)
518    }
519    #[rhai_fn(name = "index_of", return_raw, pure)]
520    pub fn index_of_with_fn_name_filter_starting_from(
521        ctx: NativeCallContext,
522        array: &mut Array,
523        filter: &str,
524        start: INT,
525    ) -> Result<INT, Box<EvalAltResult>> {
526        index_of_filter_starting_from(ctx, array, FnPtr::new(filter)?, start)
527    }
528    #[rhai_fn(return_raw, pure)]
529    pub fn some(
530        ctx: NativeCallContext,
531        array: &mut Array,
532        filter: FnPtr,
533    ) -> Result<bool, Box<EvalAltResult>> {
534        if array.is_empty() {
535            return Ok(false);
536        }
537
538        for (i, item) in array.iter().enumerate() {
539            if filter
540                .call_raw(&ctx, None, [item.clone()])
541                .or_else(|err| match *err {
542                    EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
543                        if fn_sig.starts_with(filter.fn_name()) =>
544                    {
545                        filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
546                    }
547                    _ => Err(err),
548                })
549                .map_err(|err| {
550                    Box::new(EvalAltResult::ErrorInFunctionCall(
551                        "some".to_string(),
552                        ctx.source().unwrap_or("").to_string(),
553                        err,
554                        Position::NONE,
555                    ))
556                })?
557                .as_bool()
558                .unwrap_or(false)
559            {
560                return Ok(true);
561            }
562        }
563
564        Ok(false)
565    }
566    #[rhai_fn(name = "some", return_raw, pure)]
567    pub fn some_with_fn_name(
568        ctx: NativeCallContext,
569        array: &mut Array,
570        filter: &str,
571    ) -> Result<bool, Box<EvalAltResult>> {
572        some(ctx, array, FnPtr::new(filter)?)
573    }
574    #[rhai_fn(return_raw, pure)]
575    pub fn all(
576        ctx: NativeCallContext,
577        array: &mut Array,
578        filter: FnPtr,
579    ) -> Result<bool, Box<EvalAltResult>> {
580        if array.is_empty() {
581            return Ok(true);
582        }
583
584        for (i, item) in array.iter().enumerate() {
585            if !filter
586                .call_raw(&ctx, None, [item.clone()])
587                .or_else(|err| match *err {
588                    EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
589                        if fn_sig.starts_with(filter.fn_name()) =>
590                    {
591                        filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
592                    }
593                    _ => Err(err),
594                })
595                .map_err(|err| {
596                    Box::new(EvalAltResult::ErrorInFunctionCall(
597                        "all".to_string(),
598                        ctx.source().unwrap_or("").to_string(),
599                        err,
600                        Position::NONE,
601                    ))
602                })?
603                .as_bool()
604                .unwrap_or(false)
605            {
606                return Ok(false);
607            }
608        }
609
610        Ok(true)
611    }
612    #[rhai_fn(name = "all", return_raw, pure)]
613    pub fn all_with_fn_name(
614        ctx: NativeCallContext,
615        array: &mut Array,
616        filter: &str,
617    ) -> Result<bool, Box<EvalAltResult>> {
618        all(ctx, array, FnPtr::new(filter)?)
619    }
620    #[rhai_fn(return_raw)]
621    pub fn dedup(ctx: NativeCallContext, array: &mut Array) -> Result<(), Box<EvalAltResult>> {
622        dedup_with_fn_name(ctx, array, OP_EQUALS)
623    }
624    #[rhai_fn(name = "dedup", return_raw)]
625    pub fn dedup_by_comparer(
626        ctx: NativeCallContext,
627        array: &mut Array,
628        comparer: FnPtr,
629    ) -> Result<(), Box<EvalAltResult>> {
630        if array.is_empty() {
631            return Ok(());
632        }
633
634        array.dedup_by(|x, y| {
635            comparer
636                .call_raw(&ctx, None, [x.clone(), y.clone()])
637                .unwrap_or_else(|_| Dynamic::FALSE)
638                .as_bool()
639                .unwrap_or(false)
640        });
641
642        Ok(())
643    }
644    #[rhai_fn(name = "dedup", return_raw)]
645    fn dedup_with_fn_name(
646        ctx: NativeCallContext,
647        array: &mut Array,
648        comparer: &str,
649    ) -> Result<(), Box<EvalAltResult>> {
650        dedup_by_comparer(ctx, array, FnPtr::new(comparer)?)
651    }
652    #[rhai_fn(return_raw, pure)]
653    pub fn reduce(
654        ctx: NativeCallContext,
655        array: &mut Array,
656        reducer: FnPtr,
657    ) -> Result<Dynamic, Box<EvalAltResult>> {
658        reduce_with_initial(ctx, array, reducer, Dynamic::UNIT)
659    }
660    #[rhai_fn(name = "reduce", return_raw, pure)]
661    pub fn reduce_with_fn_name(
662        ctx: NativeCallContext,
663        array: &mut Array,
664        reducer: &str,
665    ) -> Result<Dynamic, Box<EvalAltResult>> {
666        reduce(ctx, array, FnPtr::new(reducer)?)
667    }
668    #[rhai_fn(name = "reduce", return_raw, pure)]
669    pub fn reduce_with_initial(
670        ctx: NativeCallContext,
671        array: &mut Array,
672        reducer: FnPtr,
673        initial: Dynamic,
674    ) -> Result<Dynamic, Box<EvalAltResult>> {
675        if array.is_empty() {
676            return Ok(initial);
677        }
678
679        let mut result = initial;
680
681        for (i, item) in array.iter().enumerate() {
682            let item = item.clone();
683
684            result = reducer
685                .call_raw(&ctx, None, [result.clone(), item.clone()])
686                .or_else(|err| match *err {
687                    EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
688                        if fn_sig.starts_with(reducer.fn_name()) =>
689                    {
690                        reducer.call_raw(&ctx, None, [result, item, (i as INT).into()])
691                    }
692                    _ => Err(err),
693                })
694                .map_err(|err| {
695                    Box::new(EvalAltResult::ErrorInFunctionCall(
696                        "reduce".to_string(),
697                        ctx.source().unwrap_or("").to_string(),
698                        err,
699                        Position::NONE,
700                    ))
701                })?;
702        }
703
704        Ok(result)
705    }
706    #[rhai_fn(name = "reduce", return_raw, pure)]
707    pub fn reduce_with_fn_name_with_initial(
708        ctx: NativeCallContext,
709        array: &mut Array,
710        reducer: &str,
711        initial: Dynamic,
712    ) -> Result<Dynamic, Box<EvalAltResult>> {
713        reduce_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
714    }
715    #[rhai_fn(return_raw, pure)]
716    pub fn reduce_rev(
717        ctx: NativeCallContext,
718        array: &mut Array,
719        reducer: FnPtr,
720    ) -> Result<Dynamic, Box<EvalAltResult>> {
721        reduce_rev_with_initial(ctx, array, reducer, Dynamic::UNIT)
722    }
723    #[rhai_fn(name = "reduce_rev", return_raw, pure)]
724    pub fn reduce_rev_with_fn_name(
725        ctx: NativeCallContext,
726        array: &mut Array,
727        reducer: &str,
728    ) -> Result<Dynamic, Box<EvalAltResult>> {
729        reduce_rev(ctx, array, FnPtr::new(reducer)?)
730    }
731    #[rhai_fn(name = "reduce_rev", return_raw, pure)]
732    pub fn reduce_rev_with_initial(
733        ctx: NativeCallContext,
734        array: &mut Array,
735        reducer: FnPtr,
736        initial: Dynamic,
737    ) -> Result<Dynamic, Box<EvalAltResult>> {
738        if array.is_empty() {
739            return Ok(initial);
740        }
741
742        let mut result = initial;
743        let len = array.len();
744
745        for (i, item) in array.iter().rev().enumerate() {
746            let item = item.clone();
747
748            result = reducer
749                .call_raw(&ctx, None, [result.clone(), item.clone()])
750                .or_else(|err| match *err {
751                    EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
752                        if fn_sig.starts_with(reducer.fn_name()) =>
753                    {
754                        reducer.call_raw(&ctx, None, [result, item, ((len - 1 - i) as INT).into()])
755                    }
756                    _ => Err(err),
757                })
758                .map_err(|err| {
759                    Box::new(EvalAltResult::ErrorInFunctionCall(
760                        "reduce_rev".to_string(),
761                        ctx.source().unwrap_or("").to_string(),
762                        err,
763                        Position::NONE,
764                    ))
765                })?;
766        }
767
768        Ok(result)
769    }
770    #[rhai_fn(name = "reduce_rev", return_raw, pure)]
771    pub fn reduce_rev_with_fn_name_with_initial(
772        ctx: NativeCallContext,
773        array: &mut Array,
774        reducer: &str,
775        initial: Dynamic,
776    ) -> Result<Dynamic, Box<EvalAltResult>> {
777        reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
778    }
779    #[rhai_fn(name = "sort", return_raw)]
780    pub fn sort_with_fn_name(
781        ctx: NativeCallContext,
782        array: &mut Array,
783        comparer: &str,
784    ) -> Result<(), Box<EvalAltResult>> {
785        sort(ctx, array, FnPtr::new(comparer)?)
786    }
787    #[rhai_fn(return_raw)]
788    pub fn sort(
789        ctx: NativeCallContext,
790        array: &mut Array,
791        comparer: FnPtr,
792    ) -> Result<(), Box<EvalAltResult>> {
793        if array.len() <= 1 {
794            return Ok(());
795        }
796
797        array.sort_by(|x, y| {
798            comparer
799                .call_raw(&ctx, None, [x.clone(), y.clone()])
800                .ok()
801                .and_then(|v| v.as_int().ok())
802                .map(|v| match v {
803                    v if v > 0 => Ordering::Greater,
804                    v if v < 0 => Ordering::Less,
805                    0 => Ordering::Equal,
806                    _ => unreachable!(),
807                })
808                .unwrap_or_else(|| x.type_id().cmp(&y.type_id()))
809        });
810
811        Ok(())
812    }
813    #[rhai_fn(name = "sort", return_raw)]
814    pub fn sort_with_builtin(array: &mut Array) -> Result<(), Box<EvalAltResult>> {
815        if array.len() <= 1 {
816            return Ok(());
817        }
818
819        let type_id = array[0].type_id();
820
821        if array.iter().any(|a| a.type_id() != type_id) {
822            return Err(EvalAltResult::ErrorFunctionNotFound(
823                "sort() cannot be called with elements of different types".into(),
824                Position::NONE,
825            )
826            .into());
827        }
828
829        if type_id == TypeId::of::<INT>() {
830            array.sort_by(|a, b| {
831                let a = a.as_int().expect("`INT`");
832                let b = b.as_int().expect("`INT`");
833                a.cmp(&b)
834            });
835            return Ok(());
836        }
837        if type_id == TypeId::of::<char>() {
838            array.sort_by(|a, b| {
839                let a = a.as_char().expect("char");
840                let b = b.as_char().expect("char");
841                a.cmp(&b)
842            });
843            return Ok(());
844        }
845        #[cfg(not(feature = "no_float"))]
846        if type_id == TypeId::of::<crate::FLOAT>() {
847            array.sort_by(|a, b| {
848                let a = a.as_float().expect("`FLOAT`");
849                let b = b.as_float().expect("`FLOAT`");
850                a.partial_cmp(&b).unwrap_or(Ordering::Equal)
851            });
852            return Ok(());
853        }
854        if type_id == TypeId::of::<ImmutableString>() {
855            array.sort_by(|a, b| {
856                let a = a.read_lock::<ImmutableString>().expect("`ImmutableString`");
857                let b = b.read_lock::<ImmutableString>().expect("`ImmutableString`");
858                a.as_str().cmp(b.as_str())
859            });
860            return Ok(());
861        }
862        #[cfg(feature = "decimal")]
863        if type_id == TypeId::of::<rust_decimal::Decimal>() {
864            array.sort_by(|a, b| {
865                let a = a.as_decimal().expect("`Decimal`");
866                let b = b.as_decimal().expect("`Decimal`");
867                a.cmp(&b)
868            });
869            return Ok(());
870        }
871        if type_id == TypeId::of::<bool>() {
872            array.sort_by(|a, b| {
873                let a = a.as_bool().expect("`bool`");
874                let b = b.as_bool().expect("`bool`");
875                a.cmp(&b)
876            });
877            return Ok(());
878        }
879        if type_id == TypeId::of::<()>() {
880            return Ok(());
881        }
882
883        Ok(())
884    }
885    #[rhai_fn(return_raw)]
886    pub fn drain(
887        ctx: NativeCallContext,
888        array: &mut Array,
889        filter: FnPtr,
890    ) -> Result<Array, Box<EvalAltResult>> {
891        if array.is_empty() {
892            return Ok(Array::new());
893        }
894
895        let mut drained = Array::with_capacity(array.len());
896
897        let mut i = 0;
898        let mut x = 0;
899
900        while x < array.len() {
901            if filter
902                .call_raw(&ctx, None, [array[x].clone()])
903                .or_else(|err| match *err {
904                    EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
905                        if fn_sig.starts_with(filter.fn_name()) =>
906                    {
907                        filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()])
908                    }
909                    _ => Err(err),
910                })
911                .map_err(|err| {
912                    Box::new(EvalAltResult::ErrorInFunctionCall(
913                        "drain".to_string(),
914                        ctx.source().unwrap_or("").to_string(),
915                        err,
916                        Position::NONE,
917                    ))
918                })?
919                .as_bool()
920                .unwrap_or(false)
921            {
922                drained.push(array.remove(x));
923            } else {
924                x += 1;
925            }
926
927            i += 1;
928        }
929
930        Ok(drained)
931    }
932    #[rhai_fn(name = "drain", return_raw)]
933    pub fn drain_with_fn_name(
934        ctx: NativeCallContext,
935        array: &mut Array,
936        filter: &str,
937    ) -> Result<Array, Box<EvalAltResult>> {
938        drain(ctx, array, FnPtr::new(filter)?)
939    }
940    #[rhai_fn(name = "drain")]
941    pub fn drain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array {
942        let start = INT::max(range.start, 0);
943        let end = INT::max(range.end, start);
944        drain_range(array, start, end - start)
945    }
946    #[rhai_fn(name = "drain")]
947    pub fn drain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array {
948        let start = INT::max(*range.start(), 0);
949        let end = INT::max(*range.end(), start);
950        drain_range(array, start, end - start + 1)
951    }
952    #[rhai_fn(name = "drain")]
953    pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array {
954        if array.is_empty() || len <= 0 {
955            return Array::new();
956        }
957
958        let start = if start < 0 {
959            let arr_len = array.len();
960            start
961                .checked_abs()
962                .map_or(0, |n| arr_len - (n as usize).min(arr_len))
963        } else if start as usize >= array.len() {
964            return Array::new();
965        } else {
966            start as usize
967        };
968
969        let len = if len <= 0 {
970            0
971        } else if len as usize > array.len() - start {
972            array.len() - start
973        } else {
974            len as usize
975        };
976
977        array.drain(start..start + len).collect()
978    }
979    #[rhai_fn(return_raw)]
980    pub fn retain(
981        ctx: NativeCallContext,
982        array: &mut Array,
983        filter: FnPtr,
984    ) -> Result<Array, Box<EvalAltResult>> {
985        if array.is_empty() {
986            return Ok(Array::new());
987        }
988
989        let mut drained = Array::new();
990
991        let mut i = 0;
992        let mut x = 0;
993
994        while x < array.len() {
995            if !filter
996                .call_raw(&ctx, None, [array[x].clone()])
997                .or_else(|err| match *err {
998                    EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
999                        if fn_sig.starts_with(filter.fn_name()) =>
1000                    {
1001                        filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()])
1002                    }
1003                    _ => Err(err),
1004                })
1005                .map_err(|err| {
1006                    Box::new(EvalAltResult::ErrorInFunctionCall(
1007                        "retain".to_string(),
1008                        ctx.source().unwrap_or("").to_string(),
1009                        err,
1010                        Position::NONE,
1011                    ))
1012                })?
1013                .as_bool()
1014                .unwrap_or(false)
1015            {
1016                drained.push(array.remove(x));
1017            } else {
1018                x += 1;
1019            }
1020
1021            i += 1;
1022        }
1023
1024        Ok(drained)
1025    }
1026    #[rhai_fn(name = "retain", return_raw)]
1027    pub fn retain_with_fn_name(
1028        ctx: NativeCallContext,
1029        array: &mut Array,
1030        filter: &str,
1031    ) -> Result<Array, Box<EvalAltResult>> {
1032        retain(ctx, array, FnPtr::new(filter)?)
1033    }
1034    #[rhai_fn(name = "retain")]
1035    pub fn retain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array {
1036        let start = INT::max(range.start, 0);
1037        let end = INT::max(range.end, start);
1038        retain_range(array, start, end - start)
1039    }
1040    #[rhai_fn(name = "retain")]
1041    pub fn retain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array {
1042        let start = INT::max(*range.start(), 0);
1043        let end = INT::max(*range.end(), start);
1044        retain_range(array, start, end - start + 1)
1045    }
1046    #[rhai_fn(name = "retain")]
1047    pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array {
1048        if array.is_empty() || len <= 0 {
1049            return Array::new();
1050        }
1051
1052        let start = if start < 0 {
1053            let arr_len = array.len();
1054            start
1055                .checked_abs()
1056                .map_or(0, |n| arr_len - (n as usize).min(arr_len))
1057        } else if start as usize >= array.len() {
1058            return mem::take(array);
1059        } else {
1060            start as usize
1061        };
1062
1063        let len = if len < 0 {
1064            0
1065        } else if len as usize > array.len() - start {
1066            array.len() - start
1067        } else {
1068            len as usize
1069        };
1070
1071        let mut drained: Array = array.drain(..start).collect();
1072        drained.extend(array.drain(len..));
1073
1074        drained
1075    }
1076    #[rhai_fn(name = "==", return_raw, pure)]
1077    pub fn equals(
1078        ctx: NativeCallContext,
1079        array1: &mut Array,
1080        array2: Array,
1081    ) -> Result<bool, Box<EvalAltResult>> {
1082        if array1.len() != array2.len() {
1083            return Ok(false);
1084        }
1085        if array1.is_empty() {
1086            return Ok(true);
1087        }
1088
1089        let mut array2 = array2;
1090
1091        for (a1, a2) in array1.iter_mut().zip(array2.iter_mut()) {
1092            if !ctx
1093                .call_fn_raw(OP_EQUALS, true, false, &mut [a1, a2])
1094                .or_else(|err| match *err {
1095                    EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
1096                        if fn_sig.starts_with(OP_EQUALS) =>
1097                    {
1098                        if a1.type_id() == a2.type_id() {
1099                            // No default when comparing same type
1100                            Err(err)
1101                        } else {
1102                            Ok(Dynamic::FALSE)
1103                        }
1104                    }
1105                    _ => Err(err),
1106                })?
1107                .as_bool()
1108                .unwrap_or(false)
1109            {
1110                return Ok(false);
1111            }
1112        }
1113
1114        Ok(true)
1115    }
1116    #[rhai_fn(name = "!=", return_raw, pure)]
1117    pub fn not_equals(
1118        ctx: NativeCallContext,
1119        array1: &mut Array,
1120        array2: Array,
1121    ) -> Result<bool, Box<EvalAltResult>> {
1122        equals(ctx, array1, array2).map(|r| !r)
1123    }
1124}