typst_library/foundations/
ops.rs

1//! Operations on values.
2
3use std::cmp::Ordering;
4
5use ecow::eco_format;
6use typst_utils::Numeric;
7
8use crate::diag::{HintedStrResult, StrResult, bail};
9use crate::foundations::{
10    Datetime, IntoValue, Regex, Repr, SymbolElem, Value, format_str,
11};
12use crate::layout::{Alignment, Length, Rel};
13use crate::text::TextElem;
14use crate::visualize::Stroke;
15
16/// Bail with a type mismatch error.
17macro_rules! mismatch {
18    ($fmt:expr, $($value:expr),* $(,)?) => {
19        return Err(eco_format!($fmt, $($value.ty()),*).into())
20    };
21}
22
23/// Join a value with another value.
24pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
25    use Value::*;
26    Ok(match (lhs, rhs) {
27        (a, None) => a,
28        (None, b) => b,
29        (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")),
30        (Str(a), Str(b)) => Str(a + b),
31        (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")),
32        (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
33        (Bytes(a), Bytes(b)) => Bytes(a + b),
34        (Content(a), Content(b)) => Content(a + b),
35        (Content(a), Symbol(b)) => Content(a + SymbolElem::packed(b.get())),
36        (Content(a), Str(b)) => Content(a + TextElem::packed(b)),
37        (Str(a), Content(b)) => Content(TextElem::packed(a) + b),
38        (Symbol(a), Content(b)) => Content(SymbolElem::packed(a.get()) + b),
39        (Array(a), Array(b)) => Array(a + b),
40        (Dict(a), Dict(b)) => Dict(a + b),
41        (Args(a), Args(b)) => Args(a + b),
42        (a, b) => mismatch!("cannot join {} with {}", a, b),
43    })
44}
45
46/// Apply the unary plus operator to a value.
47pub fn pos(value: Value) -> HintedStrResult<Value> {
48    use Value::*;
49    Ok(match value {
50        Int(v) => Int(v),
51        Float(v) => Float(v),
52        Decimal(v) => Decimal(v),
53        Length(v) => Length(v),
54        Angle(v) => Angle(v),
55        Ratio(v) => Ratio(v),
56        Relative(v) => Relative(v),
57        Fraction(v) => Fraction(v),
58        Symbol(_) | Str(_) | Bytes(_) | Content(_) | Array(_) | Dict(_) | Datetime(_) => {
59            mismatch!("cannot apply unary '+' to {}", value)
60        }
61        Dyn(d) => {
62            if d.is::<Alignment>() {
63                mismatch!("cannot apply unary '+' to {}", d)
64            } else {
65                mismatch!("cannot apply '+' to {}", d)
66            }
67        }
68        v => mismatch!("cannot apply '+' to {}", v),
69    })
70}
71
72/// Compute the negation of a value.
73pub fn neg(value: Value) -> HintedStrResult<Value> {
74    use Value::*;
75    Ok(match value {
76        Int(v) => Int(v.checked_neg().ok_or_else(too_large)?),
77        Float(v) => Float(-v),
78        Decimal(v) => Decimal(-v),
79        Length(v) => Length(-v),
80        Angle(v) => Angle(-v),
81        Ratio(v) => Ratio(-v),
82        Relative(v) => Relative(-v),
83        Fraction(v) => Fraction(-v),
84        Duration(v) => Duration(-v),
85        Datetime(_) => mismatch!("cannot apply unary '-' to {}", value),
86        v => mismatch!("cannot apply '-' to {}", v),
87    })
88}
89
90/// Compute the sum of two values.
91pub fn add(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
92    use Value::*;
93    Ok(match (lhs, rhs) {
94        (a, None) => a,
95        (None, b) => b,
96
97        (Int(a), Int(b)) => Int(a.checked_add(b).ok_or_else(too_large)?),
98        (Int(a), Float(b)) => Float(a as f64 + b),
99        (Float(a), Int(b)) => Float(a + b as f64),
100        (Float(a), Float(b)) => Float(a + b),
101
102        (Decimal(a), Decimal(b)) => Decimal(a.checked_add(b).ok_or_else(too_large)?),
103        (Decimal(a), Int(b)) => Decimal(
104            a.checked_add(crate::foundations::Decimal::from(b))
105                .ok_or_else(too_large)?,
106        ),
107        (Int(a), Decimal(b)) => Decimal(
108            crate::foundations::Decimal::from(a)
109                .checked_add(b)
110                .ok_or_else(too_large)?,
111        ),
112
113        (Angle(a), Angle(b)) => Angle(a + b),
114
115        (Length(a), Length(b)) => Length(a + b),
116        (Length(a), Ratio(b)) => Relative(b + a),
117        (Length(a), Relative(b)) => Relative(b + a),
118
119        (Ratio(a), Length(b)) => Relative(a + b),
120        (Ratio(a), Ratio(b)) => Ratio(a + b),
121        (Ratio(a), Relative(b)) => Relative(b + a),
122
123        (Relative(a), Length(b)) => Relative(a + b),
124        (Relative(a), Ratio(b)) => Relative(a + b),
125        (Relative(a), Relative(b)) => Relative(a + b),
126
127        (Fraction(a), Fraction(b)) => Fraction(a + b),
128
129        (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")),
130        (Str(a), Str(b)) => Str(a + b),
131        (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")),
132        (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
133        (Bytes(a), Bytes(b)) => Bytes(a + b),
134        (Content(a), Content(b)) => Content(a + b),
135        (Content(a), Symbol(b)) => Content(a + SymbolElem::packed(b.get())),
136        (Content(a), Str(b)) => Content(a + TextElem::packed(b)),
137        (Str(a), Content(b)) => Content(TextElem::packed(a) + b),
138        (Symbol(a), Content(b)) => Content(SymbolElem::packed(a.get()) + b),
139
140        (Array(a), Array(b)) => Array(a + b),
141        (Dict(a), Dict(b)) => Dict(a + b),
142        (Args(a), Args(b)) => Args(a + b),
143
144        (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => {
145            Stroke::from_pair(color, thickness).into_value()
146        }
147        (Gradient(gradient), Length(thickness))
148        | (Length(thickness), Gradient(gradient)) => {
149            Stroke::from_pair(gradient, thickness).into_value()
150        }
151        (Tiling(tiling), Length(thickness)) | (Length(thickness), Tiling(tiling)) => {
152            Stroke::from_pair(tiling, thickness).into_value()
153        }
154
155        (Duration(a), Duration(b)) => Duration(a + b),
156        (Datetime(a), Duration(b)) => Datetime(a + b),
157        (Duration(a), Datetime(b)) => Datetime(b + a),
158
159        (Dyn(a), Dyn(b)) => {
160            // Alignments can be summed.
161            if let (Some(&a), Some(&b)) =
162                (a.downcast::<Alignment>(), b.downcast::<Alignment>())
163            {
164                return Ok((a + b)?.into_value());
165            }
166
167            mismatch!("cannot add {} and {}", a, b);
168        }
169
170        (a, b) => mismatch!("cannot add {} and {}", a, b),
171    })
172}
173
174/// Compute the difference of two values.
175pub fn sub(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
176    use Value::*;
177    Ok(match (lhs, rhs) {
178        (Int(a), Int(b)) => Int(a.checked_sub(b).ok_or_else(too_large)?),
179        (Int(a), Float(b)) => Float(a as f64 - b),
180        (Float(a), Int(b)) => Float(a - b as f64),
181        (Float(a), Float(b)) => Float(a - b),
182
183        (Decimal(a), Decimal(b)) => Decimal(a.checked_sub(b).ok_or_else(too_large)?),
184        (Decimal(a), Int(b)) => Decimal(
185            a.checked_sub(crate::foundations::Decimal::from(b))
186                .ok_or_else(too_large)?,
187        ),
188        (Int(a), Decimal(b)) => Decimal(
189            crate::foundations::Decimal::from(a)
190                .checked_sub(b)
191                .ok_or_else(too_large)?,
192        ),
193
194        (Angle(a), Angle(b)) => Angle(a - b),
195
196        (Length(a), Length(b)) => Length(a - b),
197        (Length(a), Ratio(b)) => Relative(-b + a),
198        (Length(a), Relative(b)) => Relative(-b + a),
199
200        (Ratio(a), Length(b)) => Relative(a + -b),
201        (Ratio(a), Ratio(b)) => Ratio(a - b),
202        (Ratio(a), Relative(b)) => Relative(-b + a),
203
204        (Relative(a), Length(b)) => Relative(a + -b),
205        (Relative(a), Ratio(b)) => Relative(a + -b),
206        (Relative(a), Relative(b)) => Relative(a - b),
207
208        (Fraction(a), Fraction(b)) => Fraction(a - b),
209
210        (Duration(a), Duration(b)) => Duration(a - b),
211        (Datetime(a), Duration(b)) => Datetime(a - b),
212        (Datetime(a), Datetime(b)) => Duration((a - b)?),
213
214        (a, b) => mismatch!("cannot subtract {1} from {0}", a, b),
215    })
216}
217
218/// Compute the product of two values.
219pub fn mul(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
220    use Value::*;
221    Ok(match (lhs, rhs) {
222        (Int(a), Int(b)) => Int(a.checked_mul(b).ok_or_else(too_large)?),
223        (Int(a), Float(b)) => Float(a as f64 * b),
224        (Float(a), Int(b)) => Float(a * b as f64),
225        (Float(a), Float(b)) => Float(a * b),
226
227        (Decimal(a), Decimal(b)) => Decimal(a.checked_mul(b).ok_or_else(too_large)?),
228        (Decimal(a), Int(b)) => Decimal(
229            a.checked_mul(crate::foundations::Decimal::from(b))
230                .ok_or_else(too_large)?,
231        ),
232        (Int(a), Decimal(b)) => Decimal(
233            crate::foundations::Decimal::from(a)
234                .checked_mul(b)
235                .ok_or_else(too_large)?,
236        ),
237
238        (Length(a), Int(b)) => Length(a * b as f64),
239        (Length(a), Float(b)) => Length(a * b),
240        (Length(a), Ratio(b)) => Length(a * b.get()),
241        (Int(a), Length(b)) => Length(b * a as f64),
242        (Float(a), Length(b)) => Length(b * a),
243        (Ratio(a), Length(b)) => Length(b * a.get()),
244
245        (Angle(a), Int(b)) => Angle(a * b as f64),
246        (Angle(a), Float(b)) => Angle(a * b),
247        (Angle(a), Ratio(b)) => Angle(a * b.get()),
248        (Int(a), Angle(b)) => Angle(a as f64 * b),
249        (Float(a), Angle(b)) => Angle(a * b),
250        (Ratio(a), Angle(b)) => Angle(a.get() * b),
251
252        (Ratio(a), Ratio(b)) => Ratio(a * b),
253        (Ratio(a), Int(b)) => Ratio(a * b as f64),
254        (Ratio(a), Float(b)) => Ratio(a * b),
255        (Int(a), Ratio(b)) => Ratio(a as f64 * b),
256        (Float(a), Ratio(b)) => Ratio(a * b),
257
258        (Relative(a), Int(b)) => Relative(a * b as f64),
259        (Relative(a), Float(b)) => Relative(a * b),
260        (Relative(a), Ratio(b)) => Relative(a * b.get()),
261        (Int(a), Relative(b)) => Relative(a as f64 * b),
262        (Float(a), Relative(b)) => Relative(a * b),
263        (Ratio(a), Relative(b)) => Relative(a.get() * b),
264
265        (Fraction(a), Int(b)) => Fraction(a * b as f64),
266        (Fraction(a), Float(b)) => Fraction(a * b),
267        (Fraction(a), Ratio(b)) => Fraction(a * b.get()),
268        (Int(a), Fraction(b)) => Fraction(a as f64 * b),
269        (Float(a), Fraction(b)) => Fraction(a * b),
270        (Ratio(a), Fraction(b)) => Fraction(a.get() * b),
271
272        (Str(a), Int(b)) => Str(a.repeat(Value::Int(b).cast()?)?),
273        (Int(a), Str(b)) => Str(b.repeat(Value::Int(a).cast()?)?),
274        (Array(a), Int(b)) => Array(a.repeat(Value::Int(b).cast()?)?),
275        (Int(a), Array(b)) => Array(b.repeat(Value::Int(a).cast()?)?),
276        (Content(a), b @ Int(_)) => Content(a.repeat(b.cast()?)),
277        (a @ Int(_), Content(b)) => Content(b.repeat(a.cast()?)),
278
279        (Int(a), Duration(b)) => Duration(b * (a as f64)),
280        (Float(a), Duration(b)) => Duration(b * a),
281        (Duration(a), Int(b)) => Duration(a * (b as f64)),
282        (Duration(a), Float(b)) => Duration(a * b),
283
284        (a, b) => mismatch!("cannot multiply {} with {}", a, b),
285    })
286}
287
288/// Compute the quotient of two values.
289pub fn div(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
290    use Value::*;
291    if is_zero(&rhs) {
292        bail!("cannot divide by zero");
293    }
294
295    Ok(match (lhs, rhs) {
296        (Int(a), Int(b)) => Float(a as f64 / b as f64),
297        (Int(a), Float(b)) => Float(a as f64 / b),
298        (Float(a), Int(b)) => Float(a / b as f64),
299        (Float(a), Float(b)) => Float(a / b),
300
301        (Decimal(a), Decimal(b)) => Decimal(a.checked_div(b).ok_or_else(too_large)?),
302        (Decimal(a), Int(b)) => Decimal(
303            a.checked_div(crate::foundations::Decimal::from(b))
304                .ok_or_else(too_large)?,
305        ),
306        (Int(a), Decimal(b)) => Decimal(
307            crate::foundations::Decimal::from(a)
308                .checked_div(b)
309                .ok_or_else(too_large)?,
310        ),
311
312        (Length(a), Int(b)) => Length(a / b as f64),
313        (Length(a), Float(b)) => Length(a / b),
314        (Length(a), Length(b)) => Float(try_div_length(a, b)?),
315        (Length(a), Relative(b)) if b.rel.is_zero() => Float(try_div_length(a, b.abs)?),
316
317        (Angle(a), Int(b)) => Angle(a / b as f64),
318        (Angle(a), Float(b)) => Angle(a / b),
319        (Angle(a), Angle(b)) => Float(a / b),
320
321        (Ratio(a), Int(b)) => Ratio(a / b as f64),
322        (Ratio(a), Float(b)) => Ratio(a / b),
323        (Ratio(a), Ratio(b)) => Float(a / b),
324        (Ratio(a), Relative(b)) if b.abs.is_zero() => Float(a / b.rel),
325
326        (Relative(a), Int(b)) => Relative(a / b as f64),
327        (Relative(a), Float(b)) => Relative(a / b),
328        (Relative(a), Length(b)) if a.rel.is_zero() => Float(try_div_length(a.abs, b)?),
329        (Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b),
330        (Relative(a), Relative(b)) => Float(try_div_relative(a, b)?),
331
332        (Fraction(a), Int(b)) => Fraction(a / b as f64),
333        (Fraction(a), Float(b)) => Fraction(a / b),
334        (Fraction(a), Fraction(b)) => Float(a / b),
335
336        (Duration(a), Int(b)) => Duration(a / (b as f64)),
337        (Duration(a), Float(b)) => Duration(a / b),
338        (Duration(a), Duration(b)) => Float(a / b),
339
340        (a, b) => mismatch!("cannot divide {} by {}", a, b),
341    })
342}
343
344/// Whether a value is a numeric zero.
345fn is_zero(v: &Value) -> bool {
346    use Value::*;
347    match *v {
348        Int(v) => v == 0,
349        Float(v) => v == 0.0,
350        Decimal(v) => v.is_zero(),
351        Length(v) => v.is_zero(),
352        Angle(v) => v.is_zero(),
353        Ratio(v) => v.is_zero(),
354        Relative(v) => v.is_zero(),
355        Fraction(v) => v.is_zero(),
356        Duration(v) => v.is_zero(),
357        _ => false,
358    }
359}
360
361/// Try to divide two lengths.
362fn try_div_length(a: Length, b: Length) -> StrResult<f64> {
363    a.try_div(b).ok_or_else(|| "cannot divide these two lengths".into())
364}
365
366/// Try to divide two relative lengths.
367fn try_div_relative(a: Rel<Length>, b: Rel<Length>) -> StrResult<f64> {
368    a.try_div(b)
369        .ok_or_else(|| "cannot divide these two relative lengths".into())
370}
371
372/// Compute the logical "not" of a value.
373pub fn not(value: Value) -> HintedStrResult<Value> {
374    match value {
375        Value::Bool(b) => Ok(Value::Bool(!b)),
376        v => mismatch!("cannot apply 'not' to {}", v),
377    }
378}
379
380/// Compute the logical "and" of two values.
381pub fn and(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
382    match (lhs, rhs) {
383        (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a && b)),
384        (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b),
385    }
386}
387
388/// Compute the logical "or" of two values.
389pub fn or(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
390    match (lhs, rhs) {
391        (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a || b)),
392        (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b),
393    }
394}
395
396/// Compute whether two values are equal.
397pub fn eq(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
398    Ok(Value::Bool(equal(&lhs, &rhs)))
399}
400
401/// Compute whether two values are unequal.
402pub fn neq(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
403    Ok(Value::Bool(!equal(&lhs, &rhs)))
404}
405
406macro_rules! comparison {
407    ($name:ident, $op:tt, $($pat:tt)*) => {
408        /// Compute how a value compares with another value.
409        pub fn $name(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
410            let ordering = compare(&lhs, &rhs)?;
411            Ok(Value::Bool(matches!(ordering, $($pat)*)))
412        }
413    };
414}
415
416comparison!(lt, "<", Ordering::Less);
417comparison!(leq, "<=", Ordering::Less | Ordering::Equal);
418comparison!(gt, ">", Ordering::Greater);
419comparison!(geq, ">=", Ordering::Greater | Ordering::Equal);
420
421/// Determine whether two values are equal.
422pub fn equal(lhs: &Value, rhs: &Value) -> bool {
423    use Value::*;
424    match (lhs, rhs) {
425        // Compare reflexively.
426        (None, None) => true,
427        (Auto, Auto) => true,
428        (Bool(a), Bool(b)) => a == b,
429        (Int(a), Int(b)) => a == b,
430        (Float(a), Float(b)) => a == b,
431        (Decimal(a), Decimal(b)) => a == b,
432        (Length(a), Length(b)) => a == b,
433        (Angle(a), Angle(b)) => a == b,
434        (Ratio(a), Ratio(b)) => a == b,
435        (Relative(a), Relative(b)) => a == b,
436        (Fraction(a), Fraction(b)) => a == b,
437        (Color(a), Color(b)) => a == b,
438        (Symbol(a), Symbol(b)) => a == b,
439        (Version(a), Version(b)) => a == b,
440        (Str(a), Str(b)) => a == b,
441        (Bytes(a), Bytes(b)) => a == b,
442        (Label(a), Label(b)) => a == b,
443        (Content(a), Content(b)) => a == b,
444        (Array(a), Array(b)) => a == b,
445        (Dict(a), Dict(b)) => a == b,
446        (Func(a), Func(b)) => a == b,
447        (Args(a), Args(b)) => a == b,
448        (Type(a), Type(b)) => a == b,
449        (Module(a), Module(b)) => a == b,
450        (Datetime(a), Datetime(b)) => a == b,
451        (Duration(a), Duration(b)) => a == b,
452        (Dyn(a), Dyn(b)) => a == b,
453
454        // Some technically different things should compare equal.
455        (&Int(i), &Float(f)) | (&Float(f), &Int(i)) => i as f64 == f,
456        (&Int(i), &Decimal(d)) | (&Decimal(d), &Int(i)) => {
457            crate::foundations::Decimal::from(i) == d
458        }
459        (&Length(len), &Relative(rel)) | (&Relative(rel), &Length(len)) => {
460            len == rel.abs && rel.rel.is_zero()
461        }
462        (&Ratio(rat), &Relative(rel)) | (&Relative(rel), &Ratio(rat)) => {
463            rat == rel.rel && rel.abs.is_zero()
464        }
465
466        _ => false,
467    }
468}
469
470/// Compare two values.
471pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> {
472    use Value::*;
473    Ok(match (lhs, rhs) {
474        (Bool(a), Bool(b)) => a.cmp(b),
475        (Int(a), Int(b)) => a.cmp(b),
476        (Float(a), Float(b)) => try_cmp_values(a, b)?,
477        (Decimal(a), Decimal(b)) => a.cmp(b),
478        (Length(a), Length(b)) => try_cmp_values(a, b)?,
479        (Angle(a), Angle(b)) => a.cmp(b),
480        (Ratio(a), Ratio(b)) => a.cmp(b),
481        (Relative(a), Relative(b)) => try_cmp_values(a, b)?,
482        (Fraction(a), Fraction(b)) => a.cmp(b),
483        (Version(a), Version(b)) => a.cmp(b),
484        (Str(a), Str(b)) => a.cmp(b),
485
486        // Some technically different things should be comparable.
487        (Int(a), Float(b)) => try_cmp_values(&(*a as f64), b)?,
488        (Float(a), Int(b)) => try_cmp_values(a, &(*b as f64))?,
489        (Int(a), Decimal(b)) => crate::foundations::Decimal::from(*a).cmp(b),
490        (Decimal(a), Int(b)) => a.cmp(&crate::foundations::Decimal::from(*b)),
491        (Length(a), Relative(b)) if b.rel.is_zero() => try_cmp_values(a, &b.abs)?,
492        (Ratio(a), Relative(b)) if b.abs.is_zero() => a.cmp(&b.rel),
493        (Relative(a), Length(b)) if a.rel.is_zero() => try_cmp_values(&a.abs, b)?,
494        (Relative(a), Ratio(b)) if a.abs.is_zero() => a.rel.cmp(b),
495
496        (Duration(a), Duration(b)) => a.cmp(b),
497        (Datetime(a), Datetime(b)) => try_cmp_datetimes(a, b)?,
498        (Array(a), Array(b)) => try_cmp_arrays(a.as_slice(), b.as_slice())?,
499
500        _ => mismatch!("cannot compare {} and {}", lhs, rhs),
501    })
502}
503
504/// Try to compare two values.
505fn try_cmp_values<T: PartialOrd + Repr>(a: &T, b: &T) -> StrResult<Ordering> {
506    a.partial_cmp(b)
507        .ok_or_else(|| eco_format!("cannot compare {} with {}", a.repr(), b.repr()))
508}
509
510/// Try to compare two datetimes.
511fn try_cmp_datetimes(a: &Datetime, b: &Datetime) -> StrResult<Ordering> {
512    a.partial_cmp(b)
513        .ok_or_else(|| eco_format!("cannot compare {} and {}", a.kind(), b.kind()))
514}
515
516/// Try to compare arrays of values lexicographically.
517fn try_cmp_arrays(a: &[Value], b: &[Value]) -> StrResult<Ordering> {
518    a.iter()
519        .zip(b.iter())
520        .find_map(|(first, second)| {
521            match compare(first, second) {
522                // Keep searching for a pair of elements that isn't equal.
523                Ok(Ordering::Equal) => None,
524                // Found a pair which either is not equal or not comparable, so
525                // we stop searching.
526                result => Some(result),
527            }
528        })
529        .unwrap_or_else(|| {
530            // The two arrays are equal up to the shortest array's extent,
531            // so compare their lengths instead.
532            Ok(a.len().cmp(&b.len()))
533        })
534}
535
536/// Test whether one value is "in" another one.
537pub fn in_(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
538    if let Some(b) = contains(&lhs, &rhs) {
539        Ok(Value::Bool(b))
540    } else {
541        mismatch!("cannot apply 'in' to {} and {}", lhs, rhs)
542    }
543}
544
545/// Test whether one value is "not in" another one.
546pub fn not_in(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
547    if let Some(b) = contains(&lhs, &rhs) {
548        Ok(Value::Bool(!b))
549    } else {
550        mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs)
551    }
552}
553
554/// Test for containment.
555pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
556    use Value::*;
557    match (lhs, rhs) {
558        (Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())),
559        (Dyn(a), Str(b)) => a.downcast::<Regex>().map(|regex| regex.is_match(b)),
560        (Str(a), Dict(b)) => Some(b.contains(a)),
561        (Str(a), Module(b)) => Some(b.scope().get(a).is_some()),
562        (a, Array(b)) => Some(b.contains(a.clone())),
563
564        _ => Option::None,
565    }
566}
567
568#[cold]
569fn too_large() -> &'static str {
570    "value is too large"
571}