Skip to main content

jay/
limits.rs

1//! The ceiling on how large a single value may be.
2//!
3//! A shape is arithmetic: `1e12 $ 0` costs one multiplication to write and
4//! eight terabytes to hold. Verbs that build a result from a requested
5//! shape or count ask here first, so an impossible request comes back as an
6//! ordinary error pointing at the expression instead of as a process the
7//! operating system kills.
8
9use crate::error::{Error, ErrorKind, Result, Span};
10
11/// The most elements one value may hold.
12///
13/// Set by what a machine can plausibly hold rather than by J or APL, both
14/// of which put no limit in the language: 2^32 elements is 32 GB of
15/// doubles, past any real working set and far short of a request that
16/// would take the process down.
17pub const MAX_ELEMENTS: usize = 1 << 32;
18
19/// The number of elements a value of this shape holds.
20///
21/// An axis of zero makes the value empty whatever the other axes say, so
22/// that case is answered before the ceiling applies.
23pub fn elements(shape: &[usize], span: Span) -> Result<usize> {
24    if shape.contains(&0) {
25        return Ok(0);
26    }
27    let mut n: u128 = 1;
28    for &d in shape {
29        n *= d as u128;
30        if n > MAX_ELEMENTS as u128 {
31            return Err(too_many(n, Some(shape), span));
32        }
33    }
34    Ok(n as usize)
35}
36
37/// A count already worked out, checked against the same ceiling.
38pub fn count(n: u128, span: Span) -> Result<usize> {
39    if n > MAX_ELEMENTS as u128 {
40        return Err(too_many(n, None, span));
41    }
42    Ok(n as usize)
43}
44
45fn too_many(n: u128, shape: Option<&[usize]>, span: Span) -> Error {
46    let e = Error::new(
47        ErrorKind::Limit,
48        format!("a result of {n} elements is past the {MAX_ELEMENTS}-element ceiling"),
49        Some(span),
50    );
51    match shape {
52        None => e,
53        Some(s) => {
54            let dims: Vec<String> = s.iter().map(usize::to_string).collect();
55            e.note(format!("the shape asked for is {}", dims.join(" ")))
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    const SPAN: Span = Span { start: 0, end: 0 };
65
66    #[test]
67    fn an_ordinary_shape_passes() {
68        assert_eq!(elements(&[2, 3, 4], SPAN).unwrap(), 24);
69        assert_eq!(elements(&[], SPAN).unwrap(), 1);
70    }
71
72    #[test]
73    fn an_empty_axis_beats_the_ceiling() {
74        assert_eq!(elements(&[usize::MAX, 0], SPAN).unwrap(), 0);
75    }
76
77    #[test]
78    fn a_product_that_would_wrap_is_refused() {
79        // 2^32 * 2^32 is 2^64, which wraps to zero in usize arithmetic.
80        let e = elements(&[1 << 32, 1 << 32], SPAN).unwrap_err();
81        assert_eq!(e.kind, ErrorKind::Limit);
82        assert!(e.msg.contains("18446744073709551616"), "{}", e.msg);
83    }
84
85    #[test]
86    fn the_ceiling_itself_is_allowed() {
87        assert!(elements(&[MAX_ELEMENTS], SPAN).is_ok());
88        assert!(elements(&[MAX_ELEMENTS + 1], SPAN).is_err());
89    }
90}