artichoke_backend/extn/core/array/
args.rs

1use std::fmt::Write as _;
2
3use crate::convert::implicitly_convert_to_int;
4use crate::extn::prelude::*;
5use crate::fmt::WriteError;
6use crate::sys::protect;
7
8#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
9pub enum ElementReference {
10    Empty,
11    Index(i64),
12    StartLen(i64, usize),
13}
14
15pub fn element_reference(
16    interp: &mut Artichoke,
17    elem: Value,
18    len: Option<Value>,
19    ary_len: usize,
20) -> Result<ElementReference, Error> {
21    if let Some(len) = len {
22        let start = implicitly_convert_to_int(interp, elem)?;
23        let len = implicitly_convert_to_int(interp, len)?;
24        return if let Ok(len) = usize::try_from(len) {
25            Ok(ElementReference::StartLen(start, len))
26        } else {
27            Ok(ElementReference::Empty)
28        };
29    }
30    let rangelen = i64::try_from(ary_len).map_err(|_| Fatal::from("Range length exceeds Integer max"))?;
31    match elem.is_range(interp, rangelen)? {
32        None => {
33            let index = implicitly_convert_to_int(interp, elem)?;
34            Ok(ElementReference::Index(index))
35        }
36        // ```
37        // [3.1.2] > a = []
38        // => []
39        // [3.1.2] > a[-1..-1]
40        // => nil
41        // ```
42        Some(protect::Range::Out) => Ok(ElementReference::Empty),
43        Some(protect::Range::Valid { start, len }) => {
44            if let Ok(len) = usize::try_from(len) {
45                Ok(ElementReference::StartLen(start, len))
46            } else {
47                Ok(ElementReference::Empty)
48            }
49        }
50    }
51}
52
53pub fn element_assignment(
54    interp: &mut Artichoke,
55    first: Value,
56    second: Value,
57    third: Option<Value>,
58    len: usize,
59) -> Result<(usize, Option<usize>, Value), Error> {
60    if let Some(elem) = third {
61        let start = implicitly_convert_to_int(interp, first)?;
62        let start = if let Some(start) = aref::offset_to_index(start, len) {
63            start
64        } else {
65            let mut message = String::new();
66            write!(&mut message, "index {start} too small for array; minimum: -{len}").map_err(WriteError::from)?;
67            return Err(IndexError::from(message).into());
68        };
69        let slice_len = implicitly_convert_to_int(interp, second)?;
70        if let Ok(slice_len) = usize::try_from(slice_len) {
71            Ok((start, Some(slice_len), elem))
72        } else {
73            let mut message = String::new();
74            write!(&mut message, "negative length ({slice_len})").map_err(WriteError::from)?;
75            Err(IndexError::from(message).into())
76        }
77    } else {
78        let rangelen = i64::try_from(len).map_err(|_| Fatal::from("Range length exceeds Integer max"))?;
79        match first.is_range(interp, rangelen)? {
80            None => {
81                let index = implicitly_convert_to_int(interp, first)?;
82                if let Some(index) = aref::offset_to_index(index, len) {
83                    Ok((index, None, second))
84                } else {
85                    let mut message = String::new();
86                    write!(&mut message, "index {index} too small for array; minimum: -{len}")
87                        .map_err(WriteError::from)?;
88                    Err(IndexError::from(message).into())
89                }
90            }
91            // ```
92            // [3.1.2] > a = []
93            // => []
94            // [3.1.2] > a[-1..-1] = 'x'
95            // (irb):13:in `[]=': -1..-1 out of range (RangeError)
96            //         from (irb):13:in `<main>'
97            //         from /usr/local/var/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
98            //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `load'
99            //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `<main>'
100            // ```
101            Some(protect::Range::Out) => Err(RangeError::with_message("out of range").into()),
102            Some(protect::Range::Valid { start, len }) => {
103                let start = usize::try_from(start)
104                    .unwrap_or_else(|_| unimplemented!("should throw RangeError (-11..1 out of range)"));
105                let len = usize::try_from(len).unwrap_or_else(|_| unreachable!("Range can't have negative length"));
106                Ok((start, Some(len), second))
107            }
108        }
109    }
110}