1#[cfg(feature = "no_std")]
12use alloc::{format, string::ToString, vec::Vec};
13
14use crate::builtins::{
15 check_array_concat_memory, check_string_concat_memory, check_string_repeat_memory, error,
16 error_with_hint,
17};
18use crate::error::BopError;
19use crate::value::{Value, values_equal};
20
21fn to_f64(v: &Value) -> Option<f64> {
35 match v {
36 Value::Int(n) => Some(*n as f64),
37 Value::Number(n) => Some(*n),
38 _ => None,
39 }
40}
41
42fn int_overflow(op: &str, line: u32) -> BopError {
43 error(line, format!("Integer overflow in `{}`", op))
44}
45
46pub fn add(left: &Value, right: &Value, line: u32) -> Result<Value, BopError> {
47 match (left, right) {
48 (Value::Int(a), Value::Int(b)) => a
49 .checked_add(*b)
50 .map(Value::Int)
51 .ok_or_else(|| int_overflow("+", line)),
52 (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
53 (Value::Int(a), Value::Number(b)) => Ok(Value::Number(*a as f64 + b)),
54 (Value::Number(a), Value::Int(b)) => Ok(Value::Number(a + *b as f64)),
55 (Value::Str(a), Value::Str(b)) => {
56 check_string_concat_memory(a.len(), b.len(), line)?;
57 Ok(Value::new_str(format!("{}{}", a, b)))
58 }
59 (Value::Str(a), b) => {
60 let b_display = format!("{}", b);
61 check_string_concat_memory(a.len(), b_display.len(), line)?;
62 Ok(Value::new_str(format!("{}{}", a, b_display)))
63 }
64 (a, Value::Str(b)) => {
65 let a_display = format!("{}", a);
66 check_string_concat_memory(a_display.len(), b.len(), line)?;
67 Ok(Value::new_str(format!("{}{}", a_display, b)))
68 }
69 (Value::Array(a), Value::Array(b)) => {
70 check_array_concat_memory(a.len(), b.len(), line)?;
71 let mut result = a.to_vec();
72 result.extend(b.to_vec());
73 Ok(Value::new_array(result))
74 }
75 _ => Err(error(
76 line,
77 format!("Can't add {} and {}", left.type_name(), right.type_name()),
78 )),
79 }
80}
81
82pub fn sub(left: &Value, right: &Value, line: u32) -> Result<Value, BopError> {
83 match (left, right) {
84 (Value::Int(a), Value::Int(b)) => a
85 .checked_sub(*b)
86 .map(Value::Int)
87 .ok_or_else(|| int_overflow("-", line)),
88 (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a - b)),
89 (Value::Int(a), Value::Number(b)) => Ok(Value::Number(*a as f64 - b)),
90 (Value::Number(a), Value::Int(b)) => Ok(Value::Number(a - *b as f64)),
91 _ => Err(error(
92 line,
93 format!(
94 "Can't use `-` with {} and {}",
95 left.type_name(),
96 right.type_name()
97 ),
98 )),
99 }
100}
101
102pub fn mul(left: &Value, right: &Value, line: u32) -> Result<Value, BopError> {
103 match (left, right) {
104 (Value::Int(a), Value::Int(b)) => a
105 .checked_mul(*b)
106 .map(Value::Int)
107 .ok_or_else(|| int_overflow("*", line)),
108 (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a * b)),
109 (Value::Int(a), Value::Number(b)) => Ok(Value::Number(*a as f64 * b)),
110 (Value::Number(a), Value::Int(b)) => Ok(Value::Number(a * *b as f64)),
111 (Value::Str(s), Value::Int(n)) | (Value::Int(n), Value::Str(s)) => {
116 if *n < 0 {
117 return Err(error(line, format!("Can't repeat a string {} times", n)));
118 }
119 let count = *n as usize;
120 check_string_repeat_memory(s.len(), count, line)?;
121 Ok(Value::new_str(s.repeat(count)))
122 }
123 (Value::Str(s), Value::Number(n)) | (Value::Number(n), Value::Str(s)) => {
124 let nf = *n;
125 if nf < 0.0 || !nf.is_finite() {
126 return Err(error(line, format!("Can't repeat a string {} times", nf)));
127 }
128 let count = nf as usize;
129 check_string_repeat_memory(s.len(), count, line)?;
130 Ok(Value::new_str(s.repeat(count)))
131 }
132 _ => Err(error(
133 line,
134 format!(
135 "Can't multiply {} and {}",
136 left.type_name(),
137 right.type_name()
138 ),
139 )),
140 }
141}
142
143pub fn div(left: &Value, right: &Value, line: u32) -> Result<Value, BopError> {
146 let a = to_f64(left).ok_or_else(|| {
147 error(
148 line,
149 format!("Can't divide {} by {}", left.type_name(), right.type_name()),
150 )
151 })?;
152 let b = to_f64(right).ok_or_else(|| {
153 error(
154 line,
155 format!("Can't divide {} by {}", left.type_name(), right.type_name()),
156 )
157 })?;
158 if b == 0.0 {
159 return Err(error_with_hint(
160 line,
161 "Division by zero",
162 "You can't divide by 0.",
163 ));
164 }
165 Ok(Value::Number(a / b))
166}
167
168pub fn rem(left: &Value, right: &Value, line: u32) -> Result<Value, BopError> {
169 match (left, right) {
170 (Value::Int(_), Value::Int(b)) if *b == 0 => Err(error_with_hint(
171 line,
172 "Modulo by zero",
173 "You can't use % with 0.",
174 )),
175 (Value::Int(a), Value::Int(b)) => a
176 .checked_rem(*b)
177 .map(Value::Int)
178 .ok_or_else(|| int_overflow("%", line)),
179 (Value::Number(_), Value::Number(b)) if *b == 0.0 => Err(error_with_hint(
180 line,
181 "Modulo by zero",
182 "You can't use % with 0.",
183 )),
184 (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a % b)),
185 (Value::Int(a), Value::Number(b)) => {
186 if *b == 0.0 {
187 return Err(error_with_hint(
188 line,
189 "Modulo by zero",
190 "You can't use % with 0.",
191 ));
192 }
193 Ok(Value::Number((*a as f64) % b))
194 }
195 (Value::Number(a), Value::Int(b)) => {
196 if *b == 0 {
197 return Err(error_with_hint(
198 line,
199 "Modulo by zero",
200 "You can't use % with 0.",
201 ));
202 }
203 Ok(Value::Number(a % (*b as f64)))
204 }
205 _ => Err(error(
206 line,
207 format!(
208 "Can't use % with {} and {}",
209 left.type_name(),
210 right.type_name()
211 ),
212 )),
213 }
214}
215
216pub fn eq(left: &Value, right: &Value) -> Value {
217 Value::Bool(values_equal(left, right))
218}
219
220pub fn not_eq(left: &Value, right: &Value) -> Value {
221 Value::Bool(!values_equal(left, right))
222}
223
224pub fn lt(left: &Value, right: &Value, line: u32) -> Result<Value, BopError> {
225 compare(left, right, |a, b| a < b, "<", line)
226}
227
228pub fn gt(left: &Value, right: &Value, line: u32) -> Result<Value, BopError> {
229 compare(left, right, |a, b| a > b, ">", line)
230}
231
232pub fn lt_eq(left: &Value, right: &Value, line: u32) -> Result<Value, BopError> {
233 compare(left, right, |a, b| a <= b, "<=", line)
234}
235
236pub fn gt_eq(left: &Value, right: &Value, line: u32) -> Result<Value, BopError> {
237 compare(left, right, |a, b| a >= b, ">=", line)
238}
239
240pub fn neg(val: &Value, line: u32) -> Result<Value, BopError> {
241 match val {
242 Value::Int(n) => n
243 .checked_neg()
244 .map(Value::Int)
245 .ok_or_else(|| int_overflow("-", line)),
246 Value::Number(n) => Ok(Value::Number(-n)),
247 _ => Err(error(line, format!("Can't negate a {}", val.type_name()))),
248 }
249}
250
251pub fn not(val: &Value) -> Value {
252 Value::Bool(!val.is_truthy())
253}
254
255fn numeric_index(idx: &Value) -> Option<i64> {
260 match idx {
261 Value::Int(n) => Some(*n),
262 Value::Number(n) => Some(*n as i64),
263 _ => None,
264 }
265}
266
267pub fn index_get(obj: &Value, idx: &Value, line: u32) -> Result<Value, BopError> {
268 match obj {
269 Value::Array(arr) => {
270 let i = numeric_index(idx).ok_or_else(|| {
271 error(
272 line,
273 format!(
274 "Can't index {} with {}",
275 obj.type_name(),
276 idx.type_name()
277 ),
278 )
279 })?;
280 let actual = if i < 0 {
281 (arr.len() as i64 + i) as usize
282 } else {
283 i as usize
284 };
285 arr.get(actual).cloned().ok_or_else(|| {
286 error(
287 line,
288 format!(
289 "Index {} is out of bounds (array has {} items)",
290 i,
291 arr.len()
292 ),
293 )
294 })
295 }
296 Value::Str(s) => {
297 let i = numeric_index(idx).ok_or_else(|| {
298 error(
299 line,
300 format!(
301 "Can't index {} with {}",
302 obj.type_name(),
303 idx.type_name()
304 ),
305 )
306 })?;
307 let chars: Vec<char> = s.chars().collect();
308 let actual = if i < 0 {
309 (chars.len() as i64 + i) as usize
310 } else {
311 i as usize
312 };
313 chars
314 .get(actual)
315 .map(|c| Value::new_str(c.to_string()))
316 .ok_or_else(|| {
317 error(
318 line,
319 format!(
320 "Index {} is out of bounds (string has {} characters)",
321 i,
322 chars.len()
323 ),
324 )
325 })
326 }
327 Value::Dict(entries) => match idx {
328 Value::Str(key) => Ok(entries
335 .iter()
336 .find(|(k, _)| k.as_str() == key.as_str())
337 .map(|(_, v)| v.clone())
338 .unwrap_or(Value::None)),
339 _ => Err(error(
340 line,
341 format!(
342 "Can't index {} with {}",
343 obj.type_name(),
344 idx.type_name()
345 ),
346 )),
347 },
348 _ => Err(error(
349 line,
350 format!("Can't index {} with {}", obj.type_name(), idx.type_name()),
351 )),
352 }
353}
354
355pub fn index_set(
356 obj: &mut Value,
357 idx: &Value,
358 val: Value,
359 line: u32,
360) -> Result<(), BopError> {
361 match obj {
362 Value::Array(arr) => {
363 let i = numeric_index(idx).ok_or_else(|| {
364 error(line, "Can't set index with these types")
365 })?;
366 let len = arr.len();
367 let actual = if i < 0 {
368 (len as i64 + i) as usize
369 } else {
370 i as usize
371 };
372 if actual >= len {
373 return Err(error(
374 line,
375 format!("Index {} is out of bounds (array has {} items)", i, len),
376 ));
377 }
378 arr.set(actual, val);
379 Ok(())
380 }
381 Value::Dict(entries) => match idx {
382 Value::Str(key) => {
383 entries.set_key(key, val);
384 Ok(())
385 }
386 _ => Err(error(line, "Can't set index with these types")),
387 },
388 _ => Err(error(line, "Can't set index with these types")),
389 }
390}
391
392fn compare(
395 left: &Value,
396 right: &Value,
397 f: impl Fn(f64, f64) -> bool,
398 op_str: &str,
399 line: u32,
400) -> Result<Value, BopError> {
401 match (left, right) {
402 (Value::Int(a), Value::Int(b)) => {
405 let result = match op_str {
406 "<" => a < b,
407 ">" => a > b,
408 "<=" => a <= b,
409 _ => a >= b,
410 };
411 Ok(Value::Bool(result))
412 }
413 (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(f(*a, *b))),
414 (Value::Int(a), Value::Number(b)) => Ok(Value::Bool(f(*a as f64, *b))),
416 (Value::Number(a), Value::Int(b)) => Ok(Value::Bool(f(*a, *b as f64))),
417 (Value::Str(a), Value::Str(b)) => {
418 let result = match op_str {
419 "<" => a < b,
420 ">" => a > b,
421 "<=" => a <= b,
422 _ => a >= b,
423 };
424 Ok(Value::Bool(result))
425 }
426 _ => Err(error(
427 line,
428 format!(
429 "Can't compare {} and {} with `{}`",
430 left.type_name(),
431 right.type_name(),
432 op_str
433 ),
434 )),
435 }
436}