1use crate::error::{DogeError, DogeResult};
2use crate::value::Value;
3
4fn normalize_index(i: i64, len: usize) -> DogeResult<usize> {
7 let len_i = len as i64;
8 let idx = if i < 0 { i + len_i } else { i };
9 if idx < 0 || idx >= len_i {
10 Err(DogeError::index_out_of_bounds(format!(
11 "index {i} is out of bounds for length {len}"
12 )))
13 } else {
14 Ok(idx as usize)
15 }
16}
17
18pub fn index_get(container: &Value, index: &Value) -> DogeResult {
21 match (container, index) {
22 (Value::List(items), Value::Int(i)) => {
23 let items = items.borrow();
24 let idx = normalize_index(*i, items.len())?;
25 Ok(items[idx].clone())
26 }
27 (Value::Str(s), Value::Int(i)) => {
28 let chars: Vec<char> = s.chars().collect();
29 let idx = normalize_index(*i, chars.len())?;
30 Ok(Value::str(chars[idx].to_string()))
31 }
32 (Value::Dict(d), Value::Str(k)) => d
33 .borrow()
34 .get(k.as_ref())
35 .cloned()
36 .ok_or_else(|| DogeError::key_error(format!("no such key: {k:?}"))),
37 (Value::List(_) | Value::Str(_), _) => Err(DogeError::type_error(format!(
38 "cannot index {} with {} (need an Int)",
39 container.describe(),
40 index.describe()
41 ))),
42 (Value::Dict(_), _) => Err(DogeError::type_error(format!(
43 "cannot index a Dict with {} (keys are Str)",
44 index.describe()
45 ))),
46 (
49 Value::Int(_)
50 | Value::Float(_)
51 | Value::Bool(_)
52 | Value::None
53 | Value::Object(_)
54 | Value::Function(_)
55 | Value::Class(_)
56 | Value::BoundMethod(_)
57 | Value::Error(_),
58 _,
59 ) => Err(DogeError::type_error(format!(
60 "cannot index {}",
61 container.describe()
62 ))),
63 }
64}
65
66pub fn index_set(container: &Value, index: &Value, value: Value) -> DogeResult<()> {
69 match (container, index) {
70 (Value::List(items), Value::Int(i)) => {
71 let mut items = items.borrow_mut();
72 let idx = normalize_index(*i, items.len())?;
73 items[idx] = value;
74 Ok(())
75 }
76 (Value::Dict(d), Value::Str(k)) => {
77 d.borrow_mut().insert(k.to_string(), value);
78 Ok(())
79 }
80 (Value::Str(_), _) => Err(DogeError::type_error(
81 "cannot assign into a Str (Str values are immutable)",
82 )),
83 (Value::List(_), _) => Err(DogeError::type_error(format!(
84 "cannot index a List with {} (need an Int)",
85 index.describe()
86 ))),
87 (Value::Dict(_), _) => Err(DogeError::type_error(format!(
88 "cannot index a Dict with {} (keys are Str)",
89 index.describe()
90 ))),
91 (
94 Value::Int(_)
95 | Value::Float(_)
96 | Value::Bool(_)
97 | Value::None
98 | Value::Object(_)
99 | Value::Function(_)
100 | Value::Class(_)
101 | Value::BoundMethod(_)
102 | Value::Error(_),
103 _,
104 ) => Err(DogeError::type_error(format!(
105 "cannot index into {}",
106 container.describe()
107 ))),
108 }
109}
110
111fn slice_bound(what: &str, v: &Value) -> DogeResult<Option<i64>> {
114 match v {
115 Value::None => Ok(None),
116 Value::Int(n) => Ok(Some(*n)),
117 _ => Err(DogeError::type_error(format!(
118 "a slice {what} must be an Int, not {}",
119 v.describe()
120 ))),
121 }
122}
123
124fn slice_step(v: &Value) -> DogeResult<i64> {
127 match v {
128 Value::None => Ok(1),
129 Value::Int(0) => Err(DogeError::value_error("a slice step cannot be zero")),
130 Value::Int(n) => Ok(*n),
131 _ => Err(DogeError::type_error(format!(
132 "a slice step must be an Int, not {}",
133 v.describe()
134 ))),
135 }
136}
137
138fn slice_indices(start: Option<i64>, end: Option<i64>, step: i64, len: usize) -> Vec<usize> {
142 let len = len as i64;
143 let (lower, upper) = if step < 0 { (-1, len - 1) } else { (0, len) };
144 let clamp = |bound: i64| {
145 let bound = if bound < 0 { bound + len } else { bound };
146 bound.clamp(lower, upper)
147 };
148 let start = match start {
149 Some(s) => clamp(s),
150 None => {
151 if step < 0 {
152 upper
153 } else {
154 lower
155 }
156 }
157 };
158 let end = match end {
159 Some(e) => clamp(e),
160 None => {
161 if step < 0 {
162 lower
163 } else {
164 upper
165 }
166 }
167 };
168
169 let mut indices = Vec::new();
170 let mut i = start;
171 if step > 0 {
172 while i < end {
173 indices.push(i as usize);
174 i += step;
175 }
176 } else {
177 while i > end {
178 indices.push(i as usize);
179 i += step;
180 }
181 }
182 indices
183}
184
185pub fn slice_get(container: &Value, start: &Value, end: &Value, step: &Value) -> DogeResult {
189 let step = slice_step(step)?;
190 let start = slice_bound("start", start)?;
191 let end = slice_bound("end", end)?;
192 match container {
193 Value::List(items) => {
194 let items = items.borrow();
195 let picked = slice_indices(start, end, step, items.len())
196 .into_iter()
197 .map(|i| items[i].clone())
198 .collect();
199 Ok(Value::list(picked))
200 }
201 Value::Str(s) => {
202 let chars: Vec<char> = s.chars().collect();
203 let picked: String = slice_indices(start, end, step, chars.len())
204 .into_iter()
205 .map(|i| chars[i])
206 .collect();
207 Ok(Value::str(picked))
208 }
209 Value::Dict(_)
212 | Value::Int(_)
213 | Value::Float(_)
214 | Value::Bool(_)
215 | Value::None
216 | Value::Object(_)
217 | Value::Function(_)
218 | Value::Class(_)
219 | Value::BoundMethod(_)
220 | Value::Error(_) => Err(DogeError::type_error(format!(
221 "cannot slice {}",
222 container.describe()
223 ))),
224 }
225}
226
227pub fn iter_value(v: &Value) -> DogeResult<Vec<Value>> {
232 match v {
233 Value::List(items) => Ok(items.borrow().clone()),
234 Value::Str(s) => Ok(s.chars().map(|c| Value::str(c.to_string())).collect()),
235 Value::Dict(entries) => Ok(entries
236 .borrow()
237 .iter()
238 .map(|(k, _)| Value::str(k))
239 .collect()),
240 Value::Int(_)
243 | Value::Float(_)
244 | Value::Bool(_)
245 | Value::None
246 | Value::Object(_)
247 | Value::Function(_)
248 | Value::Class(_)
249 | Value::BoundMethod(_)
250 | Value::Error(_) => Err(DogeError::type_error(format!(
251 "cannot loop over {}",
252 v.describe()
253 ))),
254 }
255}
256
257pub fn unpack_value(v: &Value, fixed: usize, rest: bool) -> DogeResult<Vec<Value>> {
265 let mut values = iter_value(v)
266 .map_err(|_| DogeError::type_error(format!("cannot unpack {}", v.describe())))?;
267 if rest {
268 if values.len() < fixed {
269 return Err(DogeError::value_error(format!(
270 "expected at least {fixed} values to unpack, but got {}",
271 values.len()
272 )));
273 }
274 let collected = values.split_off(fixed);
275 values.push(Value::list(collected));
276 } else if values.len() != fixed {
277 return Err(DogeError::value_error(format!(
278 "expected {fixed} values to unpack, but got {}",
279 values.len()
280 )));
281 }
282 Ok(values)
283}