Skip to main content

jay/
verb.rs

1//! Verbs and the rank machinery: the language-agnostic execution core.
2//!
3//! A `Verb` is a semantic object — a primitive or a combination of verbs —
4//! applied monadically or dyadically to arrays. Frontends lower J/APL syntax
5//! to `Verb` trees; nothing in here knows any surface syntax.
6
7use std::collections::{HashMap, HashSet};
8use std::sync::Arc;
9
10use crate::array::{Array, Buf, Data, Layout, NearInt};
11use crate::complex::{self as cx, Cx};
12use crate::dtype::DType;
13use crate::error::{Error, ErrorKind, Result, Span};
14use crate::exact::{self, Ext, Rat};
15use crate::fmt::FmtOpts;
16use crate::frontend::{ComplexOrder, EncodeDigits, FloorRule, NearCount, NestedGrade, Rules};
17use crate::par;
18use crate::simd::multiversioned;
19
20/// Infinite rank (applies to the argument as a whole).
21pub const RANK_INF: i64 = i64::MAX;
22
23/// How dyadic frames must agree. A property of the source language,
24/// fixed per compiled program.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum Agreement {
27    /// J: the shorter frame must be a prefix of the longer.
28    LeadingPrefix,
29    /// APL scalar conformability: equal frames, or one of them empty.
30    ExactOrScalar,
31}
32
33/// How close two floats have to be to count as equal.
34///
35/// Both languages compare reals with a relative tolerance: J's `9!:18`
36/// comparison tolerance, APL's `⎕CT`. Two values are equal when they differ
37/// by less than the tolerance scaled by one of their magnitudes — the
38/// smaller one in J, the larger one in APL. Both references answer strictly:
39/// a difference exactly at the threshold is not equal. Integers, characters
40/// and boxes are unaffected, and an exact bit-for-bit equality (the
41/// infinities included) is equality whatever the tolerance is.
42#[derive(Clone, Copy, Debug, PartialEq)]
43pub struct Tol {
44    /// Relative tolerance; zero compares exactly.
45    pub ct: f64,
46    /// Scale by the smaller magnitude (J) rather than the larger (APL).
47    pub by_smaller: bool,
48    /// Which reading `⌊` and `⌈` take. Unread under J, whose floor is
49    /// the tolerant comparison itself.
50    pub floor_rule: FloorRule,
51}
52
53impl Tol {
54    /// No tolerance at all — J's `u!.0`.
55    pub const EXACT: Tol = Tol { ct: 0.0, by_smaller: true, floor_rule: FloorRule::Shift };
56    /// J's default comparison tolerance, 2^-44.
57    pub const J: Tol =
58        Tol { ct: 5.684_341_886_080_802e-14, by_smaller: true, floor_rule: FloorRule::Shift };
59    /// GNU APL's default `⎕CT`.
60    pub const APL: Tol = Tol { ct: 1e-13, by_smaller: false, floor_rule: FloorRule::Shift };
61
62    /// Tolerant equality.
63    #[inline(always)]
64    pub fn eq(self, a: f64, b: f64) -> bool {
65        if a == b {
66            return true;
67        }
68        // NaN and unequal infinities fail every comparison below, which is
69        // what both references answer for them.
70        let s = if self.by_smaller {
71            a.abs().min(b.abs())
72        } else {
73            a.abs().max(b.abs())
74        };
75        (a - b).abs() < self.ct * s
76    }
77
78    /// Whose rule this is. A scalar verb is handed the tolerance and
79    /// nothing else about the dialect, and two rules below need to know
80    /// which one they are under: J reads a magnitude below the tolerance
81    /// as zero, and J's equality is total across the box boundary where
82    /// APL's reaches inside the box instead.
83    #[inline(always)]
84    pub fn is_j(self) -> bool {
85        self.by_smaller
86    }
87
88    /// Whether the tolerance reads this magnitude as zero.
89    ///
90    /// J's signum does: `* 1e_15` is 0 and `* 6e_14` is 1, the threshold
91    /// being the tolerance itself. APL's `×` is exact there. With `!.0` the
92    /// tolerance is zero, so the rule falls away with it.
93    #[inline(always)]
94    pub fn is_zero(self, y: f64) -> bool {
95        self.is_j() && y.abs() < self.ct
96    }
97
98    /// Tolerant `<`: less, and not tolerantly equal.
99    #[inline(always)]
100    pub fn lt(self, a: f64, b: f64) -> bool {
101        a < b && !self.eq(a, b)
102    }
103
104    /// Tolerant `<=`: less, or tolerantly equal.
105    #[inline(always)]
106    pub fn le(self, a: f64, b: f64) -> bool {
107        a <= b || self.eq(a, b)
108    }
109
110    /// Tolerant equality on complex values: the magnitude of the difference
111    /// against the same scale the real comparison uses. J answers
112    /// `3j4 = 3.0000000000001j4` with 1, which is this rule on magnitudes.
113    #[inline]
114    pub fn eq_cx(self, a: Cx, b: Cx) -> bool {
115        if a == b {
116            return true;
117        }
118        let (ma, mb) = (cx::abs(a), cx::abs(b));
119        let s = if self.by_smaller { ma.min(mb) } else { ma.max(mb) };
120        cx::abs(cx::sub(a, b)) < self.ct * s
121    }
122
123    /// `<. y`: the largest integer not above y, with a value just under an
124    /// integer counting as that integer.
125    ///
126    /// The three readings were each probed. J scales the gap by the
127    /// magnitude, so `<. 99.999999999995` is 100 and `<. _1e_14` is `_1`.
128    /// GNU APL shifts by the tolerance itself, so `⌊99.999999999995` is 99
129    /// — the gap of 5e¯12 is larger than `⎕CT` however big the value is —
130    /// while `⌊¯1E¯13` is 0. Dyalog scales the shift by the magnitude but
131    /// never below 1, which keeps `⌊¯1E¯14` at 0 and lifts
132    /// `⌊9.9999999999999` to 10.
133    #[inline(always)]
134    pub fn floor(self, y: f64) -> f64 {
135        if self.is_j() {
136            let c = y.ceil();
137            if self.eq(y, c) { c } else { y.floor() }
138        } else if self.floor_rule == FloorRule::Shift {
139            (y + self.ct).floor()
140        } else {
141            // The gap is compared against the step rather than added to
142            // the value: `999.99999999999 + 9.9999999999999E¯12` rounds up
143            // to a clean 1000 in double arithmetic where the exact sum is
144            // still below it, and Dyalog answers 999.
145            let c = y.ceil();
146            if c - y <= self.ct * y.abs().max(1.0) { c } else { y.floor() }
147        }
148    }
149
150    /// `>. y`: the ceiling, with a value just over an integer counting as
151    /// that integer. The three readings are [`Tol::floor`]'s, mirrored.
152    #[inline(always)]
153    pub fn ceil(self, y: f64) -> f64 {
154        if self.is_j() {
155            let f = y.floor();
156            if self.eq(y, f) { f } else { y.ceil() }
157        } else if self.floor_rule == FloorRule::Shift {
158            (y - self.ct).ceil()
159        } else {
160            let f = y.floor();
161            if y - f <= self.ct * y.abs().max(1.0) { f } else { y.ceil() }
162        }
163    }
164
165    /// `x | y`: the remainder of y on division by x, with the quotient read
166    /// tolerantly. Both references round the quotient before subtracting,
167    /// which is what makes `0.1|0.3` zero rather than a rounding error, and
168    /// each rounds it its own way.
169    ///
170    /// J takes the tolerant floor of the quotient and then answers an exact
171    /// zero whenever the product is tolerantly the dividend: `2 | 1e_14` is
172    /// `1e_14` (the quotient is nowhere near an integer) while
173    /// `2 | 4 + 1e_14` is 0 (the product 4 is tolerantly the dividend).
174    ///
175    /// GNU APL reads the remainder against the MODULUS instead: a remainder
176    /// within `⎕CT` of the modulus's magnitude is zero, so `2|1E¯14` is 0
177    /// where J keeps the `1e_14`. A remainder that rounding has pushed out
178    /// of `[0, x)` comes back into range.
179    #[inline]
180    pub fn residue(self, x: f64, y: f64) -> f64 {
181        // An infinite DIVIDEND has no residue at all under any nonzero
182        // modulus: jconsole refuses `2 | _`, `0.5 | _`, `_1 | _` and `_ | _`
183        // alike with a NaN error, and the NaN made here is what
184        // [`Tol::made_nan`] turns into that refusal. A zero modulus is the
185        // exception, because it never divides: `0 | _` is `_`.
186        if self.is_j() && y.is_infinite() && x != 0.0 {
187            return f64::NAN;
188        }
189        // An infinite modulus leaves a value of its own sign alone and
190        // sends the other one to that infinity, which is the limit both
191        // references answer with; the general formula cannot reach it,
192        // because it runs into `inf * 0`.
193        if x.is_infinite() {
194            return if y == 0.0 || (y > 0.0) == (x > 0.0) { y } else { x };
195        }
196        if x == 0.0 {
197            return y;
198        }
199        if self.is_j() {
200            let p = x * self.floor(y / x);
201            return if self.eq(y, p) { 0.0 } else { y - p };
202        }
203        // GNU APL counts the quotient as its ceiling when the gap to it is
204        // within `⎕CT` either outright or relative to the magnitude: the
205        // first is what makes `1|¯1E¯14` zero, the second what makes
206        // `1E¯15|1` zero, where the quotient is 1e15 and the gap 0.1.
207        let q = y / x;
208        let c = q.ceil();
209        let gap = c - q;
210        let k = if gap <= self.ct || gap < self.ct * q.abs().max(c.abs()) { c } else { q.floor() };
211        let r = y - x * k;
212        if r.abs() < self.ct * x.abs() {
213            0.0
214        } else if r != 0.0 && (r < 0.0) != (x < 0.0) {
215            r + x
216        } else {
217            r
218        }
219    }
220
221    /// `x * y`, with J's rule that a zero factor wins.
222    ///
223    /// J defines `0 * _` as 0 where IEEE arithmetic has no value for it, and
224    /// the rule is the factor's, not the product's: `0 * _.` is 0 too, and
225    /// `*/ 0 , _` is 0. It is also what gives `j. _` its value, because a
226    /// complex product is four real ones and `_ * 0j1` is `0j_` only when
227    /// each of them follows this rule. APL never meets the case — GNU APL
228    /// refuses an infinite operand to `×` outright — so the rule is J's
229    /// alone and a finite pair is untouched, negative zero included.
230    #[inline(always)]
231    pub fn mul(self, x: f64, y: f64) -> f64 {
232        if self.is_j() && (x == 0.0 || y == 0.0) && !(x.is_finite() && y.is_finite()) {
233            return 0.0;
234        }
235        x * y
236    }
237
238    /// Whether a result must be refused because the arithmetic MADE this
239    /// NaN: J answers `_ - _`, `_ % _`, `2 | _`, `0 ^. 0` and `! __` with a
240    /// NaN error, while a NaN the program itself wrote travels on unrefused
241    /// (`_. + 1` is `_.`). Distinguishing the two is exactly the operand
242    /// test below. APL never reaches a NaN with a value of its own, so the
243    /// rule stays J's.
244    #[inline(always)]
245    pub fn made_nan(self, r: f64, x: f64, y: f64) -> bool {
246        self.is_j() && r.is_nan() && !x.is_nan() && !y.is_nan()
247    }
248}
249
250/// One infinity or NaN in J's own spelling, for a diagnostic that has the
251/// value and not the text the user wrote.
252pub(crate) fn j_number(v: f64) -> String {
253    if v.is_nan() {
254        "_.".to_string()
255    } else if v == f64::INFINITY {
256        "_".to_string()
257    } else if v == f64::NEG_INFINITY {
258        "__".to_string()
259    } else {
260        format!("{v}")
261    }
262}
263
264/// The effect-free half of the execution context. Copyable, so a path that
265/// runs cells on other threads can carry it there; neither the output sink
266/// nor the input source can go along, which is what keeps those paths pure
267/// by construction.
268#[derive(Clone, Copy, Debug)]
269pub struct EvalCfg {
270    pub agreement: Agreement,
271    pub fmt: FmtOpts,
272    /// Comparison tolerance in force; it starts as the dialect's and `u!.n`
273    /// overrides it inside the verb it is attached to.
274    pub tol: Tol,
275    /// The dialect's settings, resolved once at compile time. A rule that
276    /// only bites at run time reads it from here rather than deducing it.
277    pub rules: Rules,
278}
279
280impl EvalCfg {
281    /// Run `f` with a context whose sink is never reached, and whose names
282    /// are empty. Only a verb that [`Verb::is_pure`] accepted is given one
283    /// of these, and an explicit definition — the only thing that reads
284    /// names — is never pure.
285    /// The near-integer admission counts, lengths and indices are read
286    /// with here. In J and in GNU APL it is the language's and no setting
287    /// moves it; Dyalog's follows `⎕CT`, so the dialect names which.
288    pub(crate) fn near(self) -> NearInt {
289        match self.rules.lang {
290            crate::Lang::J => NearInt::J,
291            crate::Lang::Apl => match self.rules.near_count {
292                NearCount::Absolute => NearInt::Apl,
293                NearCount::Tolerant => NearInt::Tolerant(self.rules.tol()),
294            },
295        }
296    }
297
298    pub(crate) fn pure<R>(self, f: impl FnOnce(&mut Ctx<'_>) -> R) -> R {
299        let mut sink = |_: &str| debug_assert!(false, "a pure verb wrote to the output sink");
300        let mut env = Env::new(Vec::new());
301        f(&mut Ctx { cfg: self, out: &mut sink, inp: None, env: &mut env, device: None })
302    }
303}
304
305/// How deep explicit definitions may call each other before libjay stops
306/// them. Recursion that runs away is a program bug; the diagnostic says so
307/// rather than letting the process die on a stack overflow.
308///
309/// The number is set by the machine stack, not by the languages: one level
310/// of a definition costs about 24 kB of stack in an unoptimised build, so
311/// the guard has to fire well inside the 2 MiB a small thread gets. It can
312/// rise when the evaluator's frames shrink.
313pub const RECURSION_LIMIT: usize = 64;
314
315/// The names a running program can reach: the values it has assigned, the
316/// verbs it has named, and the arguments bound to its parameters.
317///
318/// An explicit definition runs with a frame of its own on top: J's `=.`
319/// writes there and `=:` writes to the globals, and a name is looked for in
320/// the frame before the globals. Frames do not nest — a definition called
321/// from another sees only its own locals, which is what both references do.
322pub struct Env {
323    globals: HashMap<String, Array>,
324    frames: Vec<HashMap<String, Array>>,
325    /// The definitions currently running, innermost last; J's `$:` and
326    /// APL's `∇` name the last of them.
327    running: Vec<std::sync::Arc<crate::ir::ExplicitDef>>,
328    verbs: HashMap<String, Verb>,
329    args: Vec<Array>,
330}
331
332impl Env {
333    pub fn new(args: Vec<Array>) -> Env {
334        Env {
335            globals: HashMap::new(),
336            frames: Vec::new(),
337            running: Vec::new(),
338            verbs: HashMap::new(),
339            args,
340        }
341    }
342
343    pub fn get(&self, name: &str) -> Option<Array> {
344        if let Some(frame) = self.frames.last() && let Some(v) = frame.get(name) {
345            return Some(v.clone());
346        }
347        // A dfn written inside another reads the names the enclosing one
348        // made local: `{a←10 ⋄ {a+⍵} ⍵} 5` is 15. Only a LEXICAL parent
349        // counts, so an unrelated caller's locals stay its own — the
350        // frames below are searched, and only those whose definition this
351        // one is written inside are read.
352        if let Some(def) = self.running.last()
353            && !def.enclosing.is_empty()
354        {
355            for i in (0..self.frames.len().saturating_sub(1)).rev() {
356                if def.enclosing.contains(&self.running[i].id)
357                    && let Some(v) = self.frames[i].get(name)
358                {
359                    return Some(v.clone());
360                }
361            }
362        }
363        self.globals.get(name).cloned()
364    }
365
366    pub fn assign(&mut self, name: String, value: Array, scope: crate::ir::Scope) {
367        if scope == crate::ir::Scope::LocalDefault && self.get(&name).is_some() {
368            return;
369        }
370        let target = match (scope, self.frames.last_mut()) {
371            (crate::ir::Scope::Local | crate::ir::Scope::LocalDefault, Some(frame)) => frame,
372            _ => &mut self.globals,
373        };
374        target.insert(name, value);
375    }
376
377    pub fn define(&mut self, name: String, verb: Verb) {
378        self.verbs.insert(name, verb);
379    }
380
381    /// A global by name, reached past any frame. An operator's array
382    /// operand lives here for as long as its body runs, so that the body's
383    /// own frame does not hide it.
384    pub fn global(&self, name: &str) -> Option<Array> {
385        self.globals.get(name).cloned()
386    }
387
388    pub fn set_global(&mut self, name: String, value: Array) {
389        self.globals.insert(name, value);
390    }
391
392    pub fn unset_global(&mut self, name: &str) {
393        self.globals.remove(name);
394    }
395
396    pub fn undefine(&mut self, name: &str) {
397        self.verbs.remove(name);
398    }
399
400    pub fn verb(&self, name: &str) -> Option<&Verb> {
401        self.verbs.get(name)
402    }
403
404    pub fn arg(&self, i: usize) -> Result<Array> {
405        self.args
406            .get(i)
407            .cloned()
408            .ok_or_else(|| Error::internal("a parameter was read where none is bound"))
409    }
410
411    /// Start a definition's frame. Fails rather than overflowing the stack.
412    pub fn enter(
413        &mut self,
414        frame: HashMap<String, Array>,
415        def: std::sync::Arc<crate::ir::ExplicitDef>,
416        span: Span,
417    ) -> Result<()> {
418        if self.frames.len() >= RECURSION_LIMIT {
419            return Err(Error::new(
420                ErrorKind::Domain,
421                format!("explicit definitions called each other more than {RECURSION_LIMIT} deep"),
422                Some(span),
423            )
424            .note("a definition that recurses needs a case that stops"));
425        }
426        self.frames.push(frame);
427        self.running.push(def);
428        Ok(())
429    }
430
431    /// End a definition's frame and hand back the names it assigned.
432    pub fn leave(&mut self) -> HashMap<String, Array> {
433        self.running.pop();
434        self.frames.pop().unwrap_or_default()
435    }
436
437    /// The innermost definition now running; `$:` and `∇` name it.
438    pub fn current_def(&self) -> Option<std::sync::Arc<crate::ir::ExplicitDef>> {
439        self.running.last().cloned()
440    }
441}
442
443/// A run's source of input: one line per call, with no line terminator,
444/// and `None` once the input has ended.
445///
446/// `None` in place of the closure is a run the host attached no input to at
447/// all, which is a different thing from a source that has run out: the
448/// first is a wiring mistake in the embedding, the second is the program
449/// asking for more than it was given, and the two say so differently.
450pub type InputFn<'a> = Option<&'a mut dyn FnMut() -> Option<String>>;
451
452/// Lend an input source to a shorter-lived context. A `&mut` inside an
453/// `Option` does not reborrow on its own, so the borrow is taken apart and
454/// put back.
455pub fn reborrow_input<'s, 'a: 's>(inp: &'s mut InputFn<'a>) -> InputFn<'s> {
456    match inp {
457        Some(f) => Some(&mut **f),
458        None => None,
459    }
460}
461
462/// Execution context threaded through evaluation.
463pub struct Ctx<'a> {
464    pub cfg: EvalCfg,
465    /// Sink for explicit output (`echo`, `⎕←`, `⍞←`). stdout by default per
466    /// the sandbox contract; the host may redirect.
467    pub out: &'a mut dyn FnMut(&str),
468    /// Source for explicit input (`⍞`, `⎕`, J's `1!:1 ]1`). stdin by
469    /// default per the sandbox contract; the host may redirect, and a host
470    /// that attaches none makes every read a diagnostic.
471    pub inp: InputFn<'a>,
472    /// The names the program has bound so far.
473    pub env: &'a mut Env,
474    /// Where the run was placed. None is the CPU, which is also what every
475    /// path that cannot use a device does; only a fused node reads it.
476    pub device: Option<&'a crate::device::Device>,
477}
478
479/// How deep one application may sit inside another before libjay stops.
480///
481/// Every level costs stack frames — in the expression walk, in the rank
482/// machinery, in a verb's own tree — and a string is the interface, so a
483/// pathological one must come back as a diagnostic rather than take the
484/// host process down with it. The count is per THREAD, which is what a
485/// stack belongs to: a cell handed to another worker starts from zero on a
486/// stack of its own.
487const MAX_NESTING: usize = 400;
488
489thread_local! {
490    static NESTING: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
491}
492
493/// Report a tree already known to be too deep to walk.
494pub(crate) fn check_nesting(depth: usize, span: Span) -> Result<()> {
495    if depth > MAX_NESTING {
496        return Err(Error::new(
497            ErrorKind::Limit,
498            format!("this program nests more than {MAX_NESTING} applications deep"),
499            Some(span),
500        ));
501    }
502    Ok(())
503}
504
505/// One level of nesting, released when it goes out of scope.
506pub(crate) struct Nesting;
507
508impl Nesting {
509    /// Claim a level, or report that the program nests too deeply.
510    pub(crate) fn enter(span: Span) -> Result<Nesting> {
511        let depth = NESTING.with(|c| {
512            let d = c.get() + 1;
513            c.set(d);
514            d
515        });
516        if depth > MAX_NESTING {
517            NESTING.with(|c| c.set(c.get() - 1));
518            return Err(Error::new(
519                ErrorKind::Limit,
520                format!("this program nests more than {MAX_NESTING} applications deep"),
521                Some(span),
522            ));
523        }
524        Ok(Nesting)
525    }
526}
527
528impl Drop for Nesting {
529    fn drop(&mut self) {
530        NESTING.with(|c| c.set(c.get().saturating_sub(1)));
531    }
532}
533
534impl Ctx<'_> {
535    /// Run `f` in this context with the comparison tolerance replaced.
536    fn with_tol<R>(&mut self, tol: Tol, f: impl FnOnce(&mut Ctx<'_>) -> R) -> R {
537        let cfg = EvalCfg { tol, ..self.cfg };
538        f(&mut Ctx {
539            cfg,
540            out: &mut *self.out,
541            inp: reborrow_input(&mut self.inp),
542            env: &mut *self.env,
543            device: self.device,
544        })
545    }
546
547    /// One line of input, without its terminator.
548    ///
549    /// Both ways of having no line are errors rather than empty strings: a
550    /// program that asks for input reaches for something the host has to
551    /// have supplied, and an empty line is a line.
552    pub(crate) fn read_line(&mut self, span: Span) -> Result<String> {
553        let Some(read) = self.inp.as_deref_mut() else {
554            return Err(Error::new(
555                ErrorKind::Value,
556                "this expression reads input, and this run has no input source attached",
557                Some(span),
558            )
559            .note("attach one with Program::run_io (Rust), input= (Python), or jay_run_io (C)"));
560        };
561        read().ok_or_else(|| {
562            Error::new(ErrorKind::Value, "the input has ended: there is no line to read", Some(span))
563        })
564    }
565}
566
567/// Elementwise monadic operations (cell rank 0).
568#[derive(Clone, Copy, Debug, PartialEq, Eq)]
569pub enum ScalarMonad {
570    /// Identity on reals (J `+`, APL `+`).
571    Conj,
572    Neg,
573    Signum,
574    Recip,
575    Sqrt,
576    Exp,
577    Abs,
578    Floor,
579    Ceil,
580    /// APL `~`: logical negation; the argument must be 0 or 1.
581    Not,
582    /// J `-.`: `1 - y` on any number (a superset of logical negation).
583    OneMinus,
584    /// `y + 1` (J `>:`).
585    Inc,
586    /// `y - 1` (J `<:`).
587    Dec,
588    /// `y + y` (J `+:`).
589    Double,
590    /// `y % 2` (J `-:`); always float.
591    Halve,
592    /// `y * y` (J `*:`).
593    Square,
594    /// Natural logarithm (J `^.`, APL `⍟`); always float.
595    Ln,
596    /// `pi * y` (J/APL monadic `o.` / `○`); always float.
597    Pi,
598    /// `! y`: factorial, i.e. the gamma function at y+1. Always float, as in
599    /// J; a negative integer is a pole and yields a signed infinity.
600    Factorial,
601    /// J `j. y`: `0j1 * y`. Always complex.
602    Imaginary,
603    /// J `r. y`: `^ 0j1 * y`, the unit complex at angle y. Always complex.
604    Polar,
605}
606
607/// Elementwise dyadic operations (cell ranks 0 0).
608#[derive(Clone, Copy, Debug, PartialEq, Eq)]
609pub enum ScalarDyad {
610    Add,
611    Sub,
612    Mul,
613    /// J `%`: result is float; `0 % 0` is 0, `n % 0` is signed infinity.
614    DivJ,
615    /// APL `÷`: result is float; `0 ÷ 0` is 1, `n ÷ 0` is a domain error.
616    DivApl,
617    Min,
618    Max,
619    Pow,
620    /// `x | y`: y modulo x, sign following x; `0 | y` is y.
621    Residue,
622    Eq,
623    Ne,
624    Lt,
625    Le,
626    Gt,
627    Ge,
628    /// Least common multiple (J `*.`, APL `∧`); logical and on booleans.
629    Lcm,
630    /// Greatest common divisor (J `+.`, APL `∨`); logical or on booleans.
631    Gcd,
632    /// `x ^. y` / `x ⍟ y`: logarithm of y to base x; always float.
633    Log,
634    /// `x %: y`: the x-th root of y; always float.
635    Root,
636    /// `k o. y` / `k ○ y`: the circle function selected by the integer k —
637    /// the trigonometric, hyperbolic and inverse families, plus the two
638    /// Pythagorean forms at 0 and 4. Always float.
639    Circle,
640    /// `x ! y`: the number of ways to choose x things from y — J's argument
641    /// order. Defined for every real pair through the gamma function.
642    Binomial,
643    /// J `x j. y`: `x + 0j1 * y`. Always complex.
644    MakeComplex,
645    /// J `x r. y`: `x * ^ 0j1 * y`, i.e. polar coordinates. Always complex.
646    PolarBy,
647}
648
649/// How a value is put into a box.
650#[derive(Clone, Copy, Debug, PartialEq, Eq)]
651pub enum Enclose {
652    /// J `<`: every value becomes a box.
653    Always,
654    /// APL `⊂`: a simple scalar is its own enclosure, so `⊂5` is `5`.
655    ExceptSimpleScalar,
656}
657
658/// Monadic meaning of a primitive.
659#[derive(Clone, Copy, Debug, PartialEq, Eq)]
660pub enum MonadOp {
661    Scalar(ScalarMonad),
662    /// Shape as an integer vector (J `$`, APL `⍴`).
663    ShapeOf,
664    /// Item count as a scalar (J `#`, APL `≢`).
665    Tally,
666    /// All elements as a vector (J/APL `,`).
667    Ravel,
668    /// Each item raveled into a row of a table (J `,.`). The answer never
669    /// has a rank below two, so an atom becomes a one-by-one table.
670    RavelItems,
671    /// Reverse the axes (J `|:`, APL `⍉`).
672    TransposeAxes,
673    /// `{ y`: catalogue — one element from each item of y, in every
674    /// combination, each combination boxed.
675    Catalogue,
676    /// J `5!:1`: the atomic representation of the entity a boxed name
677    /// stands for, boxed. A noun stands for itself, so its representation
678    /// is the pair `('0'; <value)`.
679    AtomicRep,
680    /// `e. y`: raze-in — for every element of y, which items of the raze
681    /// of y it holds.
682    RazeIn,
683    /// First item (J `{.`).
684    Head,
685    /// All but the first item (J `}.`).
686    Behead,
687    /// Last item (J `{:`); a cell of fills when there are no items.
688    Tail,
689    /// All but the last item (J `}:`).
690    Curtail,
691    /// Reverse the items, i.e. along the leading axis (J `|.`, APL `⊖`).
692    Reverse,
693    /// Distinct items in first-occurrence order (J `~.`, APL `∪`).
694    Nub,
695    /// The stable permutation that sorts the items ascending (J `/:`, APL `⍋`).
696    GradeUp { origin: i64 },
697    /// The stable permutation that sorts the items descending (J `\:`, APL `⍒`).
698    GradeDown { origin: i64 },
699    /// J `i.`: integers 0.. filling shape |y|, reversed along negative axes.
700    IotaJ,
701    /// APL `⍳` on a scalar: origin .. origin+y-1.
702    IotaApl { origin: i64 },
703    /// Print the formatted argument, yield an empty array (J `echo`).
704    Echo,
705    /// J `1!:1 y`: one line from the input source as a character vector,
706    /// the terminator dropped. `y` names the stream: 1 is stdin, which the
707    /// sandbox opens, and everything else is a file, which it does not.
708    ReadStream,
709    /// J `3!:0 y`: the code J gives the argument's element type.
710    TypeCode,
711    /// The argument itself (APL `⊢`).
712    Same,
713    /// J `":` / APL `⍕`: the argument as the characters that display it.
714    /// A rank-0 argument gives a character vector, a rank-r one a character
715    /// array of rank r (the display's lines, padded to one width).
716    Format,
717    /// J `#.` / APL monadic base-2 decode: a vector of digits as one number.
718    DecodeBits,
719    /// J `#:`: base-2 encode. The width comes from the largest magnitude in
720    /// the whole argument, so the verb has infinite rank; the digits become
721    /// a new trailing axis.
722    EncodeBits,
723    /// J `,:`: a leading axis of one (shape `2 3` becomes `1 2 3`).
724    Itemize,
725    /// APL `⍪`: the argument as a matrix — one row per item, that item's
726    /// elements ravelled. A scalar becomes 1×1, a vector n×1.
727    TableOf,
728    /// J `<` / APL `⊂`: the argument as one box.
729    Enclose(Enclose),
730    /// J `>` / APL `⊃`: open a box (rank 0, so the frame reassembles the
731    /// contents, filling where their shapes differ). A non-box opens to
732    /// itself.
733    Open,
734    /// J `;`: raze — the items of the opened boxes, catenated.
735    Raze,
736    /// APL `↑`: the first element, disclosed; the type's fill when there
737    /// is none.
738    First,
739    /// APL `∊`: enlist — every leaf element, in ravel order, as a vector.
740    Enlist,
741    /// APL `≡`: depth — 0 for a simple scalar, 1 for a simple array, one
742    /// more than the deepest content for a box.
743    Depth {
744        /// Negate the depth of an array whose items differ in depth or in
745        /// shape, as the Dyalog line does.
746        signed: bool,
747    },
748    /// J `I.` / APL `⍸`: index `i` repeated `y[i]` times. J applies at
749    /// rank 1; APL applies whole, and answers a rank-2-or-higher argument
750    /// with one boxed coordinate vector per occurrence.
751    Indices { origin: i64, boxed_coords: bool },
752    /// J `i:`: the integers from `-y` to `y`, one step apart.
753    Steps,
754    /// J `x:`: the argument in the exact types — extended when every value
755    /// is whole, rational otherwise.
756    ToExact,
757    /// J `p:`: the y-th prime, counting from zero.
758    NthPrime,
759    /// J `q:`: y's prime factors, ascending, with multiplicity.
760    PrimeFactors,
761    /// J `%.` / APL `⌹`: the inverse, or the least-squares pseudo-inverse.
762    MatrixInverse,
763    /// J `?` / `?.` and APL `?`: roll. Each element of y is replaced by a
764    /// random value below it, counted from `origin`. `fixed` restarts the
765    /// generator at its fixed seed, which is J's `?.`; `float_at_zero` is
766    /// J's `? 0`, a uniform double, where APL refuses a zero.
767    Roll { origin: i64, fixed: bool, float_at_zero: bool },
768    /// J `+. y` (rectangular) and `*. y` (polar): the two parts of a
769    /// complex number as a two-element vector, which becomes a new trailing
770    /// axis. A real argument is the pair `y 0` / `|y| 0`.
771    ComplexParts { polar: bool },
772    /// J `=`: self-classify — one row per distinct item, holding 1 where
773    /// that item stands among y's items.
774    SelfClassify,
775    /// J `~:` / APL `≠`: nub sieve — 1 at each item that has not occurred
776    /// before.
777    NubSieve,
778    /// J `u:` / APL `⎕UCS`: codepoints become characters, characters become
779    /// their codepoints. `pass_chars` is J's monad, which answers characters
780    /// with themselves rather than converting them.
781    Unicode { pass_chars: bool },
782    /// J `s:`: the argument's text as interned symbols. A character list
783    /// is cut on its own leading delimiter; a character table gives one
784    /// name per row; a boxed argument gives one name per box.
785    Symbols,
786    /// J `$.`: the argument in sparse form — every axis sparse, zero the
787    /// sparse element. A scalar has no axis to store along and stays dense.
788    Sparse,
789    /// J `;:`: J's own tokeniser over a character list, one box per word.
790    Words,
791    /// APL `⊆` (Dyalog): nest — enclose y unless it is already nested, or
792    /// a simple scalar, which cannot be enclosed any further.
793    Nest,
794    /// J `L.`: the boxing level — 0 for anything unboxed, one more than the
795    /// deepest content otherwise.
796    LevelOf,
797    /// J `{::`: y's box structure with every leaf replaced by the path that
798    /// fetches it — a boxed list holding one index per level descended.
799    MapPaths,
800    /// J `p.`: the roots of the polynomial whose ascending coefficients y
801    /// holds, as the boxed pair `multiplier ; roots`; a boxed argument of
802    /// that form converts back to coefficients.
803    PolyRoots,
804    /// J `p..`: the derivative of the polynomial y's ascending coefficients
805    /// describe, again as coefficients.
806    PolyDeriv,
807    /// J `A.`: the anagram index of the permutation y's items rank as.
808    AnagramIndex,
809    /// J `C.`: a direct permutation as its cycles, or a boxed list of
810    /// cycles as the direct permutation. The argument's type decides which.
811    CycleForm,
812    /// APL `↓`: split — each major cell of y enclosed, the leading axis
813    /// becoming the shape of the result.
814    Split,
815    /// J `". y` / APL `⍎ y`: compile the characters of y as a program of
816    /// this language and run it here, over the names the caller already
817    /// has. Nothing else about the sandbox changes: the nested program can
818    /// reach exactly what the outer one can.
819    Execute { apl: bool },
820    /// J `$.^:_1`: the obverse of sparse — the argument with every position
821    /// materialised. A dense argument is already the answer.
822    Dense,
823    /// J `p:^:_1`: the obverse of the y-th prime — how many primes stand
824    /// below y, which sends a prime back to its own index.
825    PrimeCount,
826    /// J `I.^:_1`: the obverse of indices — how many times each index from
827    /// zero to the largest occurs in y.
828    IndicesInverse,
829    /// Present in the language, not implemented: named feature.
830    NotYet(&'static str),
831    /// No monadic meaning exists for this primitive in its language.
832    None,
833}
834
835/// Dyadic meaning of a primitive.
836#[derive(Clone, Copy, Debug, PartialEq, Eq)]
837pub enum DyadOp {
838    Scalar(ScalarDyad),
839    /// x $ y / x ⍴ y: lay out shape x, reusing y — its ITEMS in J, its
840    /// ravel in APL.
841    Reshape,
842    /// x {. y / x ↑ y: per-axis take, negative from the end, overtake fills.
843    Take,
844    /// x }. y / x ↓ y: per-axis drop, negative from the end.
845    Drop,
846    /// y (APL `⊢`).
847    Right,
848    /// x (APL `⊣`).
849    Left,
850    /// `x |. y`: rotate axis k of y left by `x[k]` (negative rotates right).
851    Rotate,
852    /// `x ⌽ y` and `x ⊖ y`: rotate ONE axis of y — the last one when
853    /// `last`, the leading one otherwise — by one amount per vector along
854    /// it. APL's left argument is a whole array shaped like y with that
855    /// axis removed, not J's one amount per axis.
856    RotateApl { last: bool },
857    /// Catenate along the LEADING axis (J `,`, APL `⍪`).
858    AppendLeading,
859    /// Catenate along the LAST axis (APL `,`).
860    AppendLast,
861    /// x i. y / x ⍳ y: the index in x's items of each cell of y, or
862    /// `origin + #items(x)` when absent. `vector_left` is the Dyalog
863    /// dialect's rule that the left argument must be a vector.
864    IndexOf { origin: i64, vector_left: bool },
865    /// x e. y: is each cell of x, shaped like y's items, an item of y?
866    MemberJ,
867    /// x ∊ y: does each ELEMENT of x occur anywhere in y?
868    MemberApl,
869    /// x { y: each integer atom of x selects an item of y (negative from
870    /// the end).
871    From,
872    /// x -: y / x ≡ y: same shape and same values; never a shape error.
873    Match,
874    /// The negation of `Match` (APL `≢`).
875    NotMatch,
876    /// x /: y and x \: y: x's items reordered by the grade of y's items.
877    GradeSelect { down: bool },
878    /// `x # y` (J), `x/y` and `x⌿y` (APL): item i of y repeated `x[i]` times.
879    /// A one-element x applies to every item.
880    Copy,
881    /// `x #. y` / `x ⊥ y`: mixed-radix decode. A scalar x is the base for
882    /// every digit; otherwise x and y have the same length.
883    Decode,
884    /// `x #: y` / `x ⊤ y`: mixed-radix encode. The digits become the LEADING
885    /// axis of the result, which is what makes one operation serve J's
886    /// per-atom `#:` (right rank 0) and APL's `⊤` (right rank infinite).
887    Encode,
888    /// `x ⍋ y` and `x ⍒ y`: the items of y graded by where each of their
889    /// characters sits in the collating array x.
890    CollateGrade { down: bool, origin: i64 },
891    /// `x |: y`: y with the named axes moved to the end. A boxed x groups
892    /// axes to be run together, which is the diagonal.
893    TransposeJ,
894    /// `x ⍉ y`: x says, for each axis of y, which axis of the result it
895    /// becomes; a repeated destination runs those axes together.
896    TransposeApl,
897    /// `x ⊥ y` on arguments of rank 2 and above: the inner product `+.×`
898    /// over the LAST axis of x and the LEADING axis of y.
899    DecodeApl,
900    /// `x ⊤ y` where x has rank 2 or more: x's LEADING axis is the radix,
901    /// and its remaining axes frame the result along with y's.
902    EncodeApl,
903    /// `x ,: y`: the two arguments as the items of a new leading axis.
904    Laminate,
905    /// J `;`: link — `(<x)` before y, which is taken as it is when it is
906    /// already boxed and boxed when it is not.
907    Link,
908    /// APL vector notation: x is one more item in front of the strand y.
909    Strand,
910    /// J `x I. y` / APL `x ⍸ y`: which interval of the ascending x each cell
911    /// of y falls in. The field is what the language adds to the count of
912    /// items below it: nothing in J, `⎕IO - 1` in APL.
913    IntervalIndex { offset: i64, closed: bool },
914    /// J `x i: y`: where each cell of y LAST sits among the items of x.
915    IndexOfLast { origin: i64 },
916    /// J `x %. y` / APL `x ⌹ y`: the least-squares solution of `y a = x`.
917    MatrixDivide,
918    /// APL `x ⊂ y`: partitioned enclose — a 1 in x opens a partition, a 0
919    /// continues it, and a leading run of 0s drops those items.
920    PartitionEnclose,
921    /// Dyalog's partitioned enclose: the left argument counts the
922    /// partitions to open before each item, rather than flagging where
923    /// one begins.
924    PartitionCounts,
925    /// APL `x ⌷ y`: one scalar index per axis of y.
926    Squad {
927        origin: i64,
928        /// Read the index as one item per LEADING axis, so fewer items
929        /// than the rank take the trailing axes whole (the Dyalog line).
930        /// Otherwise there is one item per axis, all of them named.
931        leading: bool,
932    },
933    /// One bracket slot of APL indexing: axis `axis` of y selected by x.
934    /// `rank`, when it is not zero, is the number of slots the brackets
935    /// held, checked by the slot that sees the whole array.
936    SelectAxis { axis: usize, rank: usize, origin: i64 },
937    /// J `x {:: y`: follow the path x into y, opening a level a step.
938    Fetch,
939    /// J `x p. y`: the polynomial with ascending coefficients x at y. A
940    /// boxed x is the `multiplier ; roots` form of the same polynomial.
941    PolyEval,
942    /// J `x p.. y`: the integral of the polynomial y's coefficients
943    /// describe, with x as the constant term.
944    PolyIntegral,
945    /// APL `x ⍕ y`: format by specification — one width and precision per
946    /// column of the last axis, or one pair for the whole argument.
947    FormatSpec,
948    /// J `x ": y`: format by specification — one `w j d` complex value per
949    /// column of the last axis, or one for the whole argument. A negative
950    /// width asks for the exponential form; a value that does not fit its
951    /// field is written as asterisks.
952    FormatSpecJ,
953    /// J `x ". y`: the numbers a line of text spells, with x standing in
954    /// for every word that is not one.
955    ParseNumbers,
956    /// J `x ;: y`: the sequential machine x describes, run over y.
957    SequentialMachine,
958    /// J `x m b. y`: the boolean function whose truth table `m` numbers,
959    /// on two bits for `m` below 16 and on every bit of two integers for
960    /// `m` from 16 to 31.
961    TruthTable(u8),
962    /// J `x x: y`: which exact form. 1 is the rational one, 2 the pair of
963    /// numerator and denominator, `_1` the conversion back to a machine
964    /// number, `_2` the argument unchanged.
965    ExactForm,
966    /// J `x ? y` / `x ?. y` and APL `x ? y`: deal — x distinct values from
967    /// the y below `origin + y`.
968    Deal { origin: i64, fixed: bool },
969    /// J `+:` and `*:` / APL `⍱` and `⍲`: the two boolean operations that
970    /// have no other reading. Both arguments must be 0 or 1.
971    Boolean(BoolDyad),
972    /// J `x -. y` / APL `x ~ y`: the items of x that are not items of y.
973    Less,
974    /// APL `x ∪ y`: x's items, then y's items that x does not already have.
975    Union,
976    /// APL `x ∩ y`: the items of x that y also has, in x's order.
977    Intersect,
978    /// J `x A. y`: y's items under the x-th permutation of the items, the
979    /// permutations counted in lexicographic order.
980    AnagramFrom,
981    /// J `x C. y`: y's items permuted by x — a direct permutation, or a
982    /// boxed list of cycles.
983    Permute,
984    /// J `x E. y` / APL `x ⍷ y`: 1 at each position of y where a copy of x
985    /// begins.
986    FindSeq,
987    /// J `x u: y`: which conversion — 3 and 4 take characters to
988    /// codepoints, 8 and 10 take codepoints to characters.
989    UnicodeForm,
990    /// J `x p: y`: which fact about primes — `_1` counts the primes below
991    /// y, 0 asks whether y is composite, 1 whether it is prime, and `x` of
992    /// magnitude 4 steps to the next or previous prime.
993    PrimeMeta,
994    /// J `x q: y`: the exponents of the first x primes in y, or, for `__`,
995    /// the distinct primes over their exponents as a 2-row table.
996    PrimeExponents,
997    /// J `x s:`: the numbered symbol forms. 4 gives the names as a padded
998    /// character table, 5 gives them as boxes.
999    SymbolForm,
1000    /// APL `x ⊃ y`: pick — follow the path x into y, opening a level a step.
1001    Pick { origin: i64 },
1002    /// APL `x \ y` and `x ⍀ y`: expand — a 1 in x takes the next item of y,
1003    /// a 0 puts a fill in its place.
1004    Expand,
1005    /// J `x 1!:2 y`: write x, formatted as it displays and followed by a
1006    /// newline, to the stream y; the value is x. Stream 2 is stdout, which
1007    /// the sandbox opens, and everything else is a file, which it does not.
1008    WriteStream,
1009    /// J `x $.`: the numbered sparse forms. `_1` gives the shape, the
1010    /// sparse axes and the sparse element boxed; 0 converts between the two
1011    /// storage kinds; 1 makes a new sparse array from a shape; 2 to 5 and 7
1012    /// ask about the argument; 8 drops the stored entries that hold the
1013    /// sparse element.
1014    SparseForm,
1015    NotYet(&'static str),
1016    None,
1017}
1018
1019/// The dyadic operations that read and write booleans and nothing else.
1020#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1021pub enum BoolDyad {
1022    /// J `+:`, APL `⍱`: neither.
1023    Nor,
1024    /// J `*:`, APL `⍲`: not both.
1025    Nand,
1026}
1027
1028/// A primitive verb: a name for diagnostics, both valence meanings, and
1029/// J-style ranks [monadic, dyadic-left, dyadic-right].
1030#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1031pub struct Prim {
1032    pub name: &'static str,
1033    pub monad: MonadOp,
1034    pub dyad: DyadOp,
1035    pub ranks: [i64; 3],
1036}
1037
1038/// Which windowed application a [`Verb::Windowed`] performs. One variant
1039/// covers all three because the work is the same: the verb is applied to a
1040/// run of consecutive items, and only the choice of runs differs.
1041#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1042pub enum WindowKind {
1043    /// J `u\`: the monad applies u to every prefix, the dyad `x u\ y` to
1044    /// every window of x items.
1045    Prefix,
1046    /// J `u\.`: the monad applies u to every suffix; the dyad (outfix) is
1047    /// not implemented.
1048    Suffix,
1049    /// APL `f\` and `f⍀`: the monad is the scan, which is the prefix
1050    /// application. APL has no dyadic scan — `x\y` is expand, a function of
1051    /// its own — so the dyad reports that instead.
1052    Scan,
1053}
1054
1055/// How many times a [`Verb::PowerN`] applies its verb.
1056#[derive(Clone, Debug, PartialEq, Eq)]
1057pub enum Power {
1058    /// Exactly `n` applications; 0 is the identity.
1059    Times(u64),
1060    /// Iterate until a result matches the one before it (J `u^:_`).
1061    Converge,
1062    /// A list of counts: one answer per count, framed (`u^:(0 1 2)`). A
1063    /// boxed count is spelled this way too — `u^:(<n)` is `u^:(i.n)`.
1064    Each(Vec<u64>),
1065    /// Every result on the way to convergence, framed (`u^:a:`).
1066    ConvergeTrace,
1067}
1068
1069/// Iterations `Power::Converge` allows before giving up.
1070const CONVERGE_LIMIT: usize = 1 << 20;
1071
1072/// The results `u M.` has already computed, keyed by the arguments that
1073/// produced them. Shared by every clone of the derived verb, which is what
1074/// makes the cache survive from one application to the next.
1075pub type MemoCache = Arc<std::sync::Mutex<HashMap<Vec<u64>, Array>>>;
1076
1077/// What a user-written operator was given for an operand.
1078///
1079/// Dyalog lets an ARRAY stand where a function operand belongs, and the
1080/// body then reads `⍺⍺` or `⍵⍵` as that array: `2{⍺⍺+⍵}3` is 5.
1081#[derive(Clone, Debug)]
1082pub enum Operand {
1083    Func(Box<Verb>),
1084    Value(Box<Array>),
1085}
1086
1087impl Operand {
1088    /// Name for diagnostics.
1089    pub fn name(&self) -> String {
1090        match self {
1091            Operand::Func(v) => v.name(),
1092            Operand::Value(_) => "n".to_string(),
1093        }
1094    }
1095
1096    fn is_value(&self) -> bool {
1097        matches!(self, Operand::Value(_))
1098    }
1099}
1100
1101/// An operator dfn's body, parsed once for each reading of its operands.
1102///
1103/// Whether `⍺⍺` names a function or an array decides how the body PARSES,
1104/// not merely what it computes: `⍺⍺+⍵` is a train under the first reading
1105/// and a sum under the second. The body is therefore parsed both ways —
1106/// four ways when it takes a right operand as well — when the dfn is
1107/// defined, and the operands choose the reading when they arrive.
1108#[derive(Debug)]
1109pub struct OpDef {
1110    /// Indexed by `(⍺⍺ is an array) + 2 × (⍵⍵ is an array)`. `Err` holds
1111    /// what the body said when it would not parse that way, so choosing
1112    /// that reading reports the body's own complaint.
1113    pub readings: [std::result::Result<Verb, String>; 4],
1114}
1115
1116impl OpDef {
1117    /// The one reading with a body under every combination of operands:
1118    /// what a dfn that mentions neither `⍺⍺` nor `⍵⍵` would need, and the
1119    /// shape a frontend uses before it has parsed the alternatives.
1120    pub fn uniform(v: Verb) -> OpDef {
1121        OpDef { readings: [Ok(v.clone()), Ok(v.clone()), Ok(v.clone()), Ok(v)] }
1122    }
1123
1124    /// The body as it parses for these operands.
1125    pub fn pick(&self, alpha: &Operand, omega: Option<&Operand>) -> Result<&Verb> {
1126        let i = usize::from(alpha.is_value())
1127            | (usize::from(omega.is_some_and(Operand::is_value)) << 1);
1128        self.readings[i].as_ref().map_err(|msg| Error::new(ErrorKind::Parse, msg.clone(), None))
1129    }
1130
1131    /// Every reading that parsed, for the questions asked of the derived
1132    /// verb before its operands have chosen one.
1133    fn bodies(&self) -> impl Iterator<Item = &Verb> {
1134        self.readings.iter().filter_map(|r| r.as_ref().ok())
1135    }
1136}
1137
1138/// A verb: primitive or derived. Language-agnostic; frontends decide which
1139/// combinations their syntax produces (e.g. APL `+/` becomes
1140/// `Rank(Reduce(+), [1,1,1])` — reduce the last axis).
1141#[derive(Clone, Debug)]
1142pub enum Verb {
1143    Prim(Prim),
1144    /// Apply the verb to cells of the given ranks (J `"`, APL `⍤`).
1145    Rank(Box<Verb>, [i64; 3]),
1146    /// Insert the verb between items, folding right to left (J `/`, APL `⌿`).
1147    Reduce(Box<Verb>),
1148    /// APL `f/` and `f⌿`: the same insert monadically, and the N-WISE
1149    /// REDUCTION dyadically — `n f/ y` folds each window of n items along
1150    /// the leading axis. J's `u/` is the table dyadically, so the two
1151    /// spellings cannot share a node.
1152    NWise(Box<Verb>),
1153    /// Apply the verb to runs of consecutive items (J `\` and `\.`, APL
1154    /// `\` and `⍀`). The valence chooses the runs; see [`WindowKind`].
1155    Windowed(Box<Verb>, WindowKind),
1156    /// J `u~`, APL `u⍨`: monad `u~ y` = `y u y`; dyad `x u~ y` = `y u x`.
1157    Commute(Box<Verb>),
1158    /// J `u^:n`, APL `u⍣n`: apply the verb n times, or to convergence.
1159    PowerN(Box<Verb>, Power),
1160    /// (f g h) y = (f y) g (h y);  x (f g h) y = (x f y) g (x h y).
1161    Fork(Box<Verb>, Box<Verb>, Box<Verb>),
1162    /// (n g h) y = n g (h y);  x (n g h) y = n g (x h y).
1163    NounFork(Array, Box<Verb>, Box<Verb>),
1164    /// (f g) y = y f (g y);  x (f g) y = x f (g y).  (J hook)
1165    Hook(Box<Verb>, Box<Verb>),
1166    /// f@:g / [: f g:  monad f (g y);  dyad f (x g y).
1167    Atop(Box<Verb>, Box<Verb>),
1168    /// f&:g:  monad f (g y);  dyad (g x) f (g y). J's `&` is this wrapped in
1169    /// [`Verb::Rank`] at g's monadic rank; `&:` is this on its own.
1170    Compose(Box<Verb>, Box<Verb>),
1171    /// `m&v`: the noun bonded as the left argument — monad `m v y`. J gives
1172    /// a bond no dyadic valence at all.
1173    BondLeft(Array, Box<Verb>),
1174    /// `u&n`: the noun bonded as the right argument — monad `y u n`.
1175    BondRight(Box<Verb>, Array),
1176    /// J `u&.>` and APL `u¨`: open each box, apply u, put the result back
1177    /// in a box. Cell rank 0 on every side, so the frames pair as usual.
1178    Each(Box<Verb>, Enclose),
1179    /// J `u&.,`: the other under that is not built out of an inverse. `,`
1180    /// has no obverse of its own — a ravel says nothing about the shape it
1181    /// came from — but under a FIXED shape it has one, so `u&., y` is u
1182    /// over the ravel, reshaped to y's own shape. The reference gives it
1183    /// one valence only.
1184    UnderRavel(Box<Verb>),
1185    /// J `u!.n`: apply u with the comparison tolerance replaced by n.
1186    Fit(Box<Verb>, f64),
1187    /// J `x m} y`: y with the items at the indices m replaced by x.
1188    Amend(Array),
1189    /// J `u}`: the same amend, with the indices computed rather than
1190    /// written — `u} y` is `(u y)} y` and `x u} y` is `x (x u y)} y`.
1191    AmendVerb(Box<Verb>),
1192    /// J `|.!.f`: shift instead of rotate, the vacated positions taking the
1193    /// fill f.
1194    ShiftFill(Array),
1195    /// J `u M.`: u, with the results it has already computed kept and
1196    /// returned again for the same arguments. The cache belongs to this
1197    /// derived verb, so it lives exactly as long as the program does.
1198    Memo(Box<Verb>, MemoCache),
1199    /// J `u L: n` and `u S: n`: apply u to every subarray at boxing level
1200    /// n or below. `L:` puts each result back where its operand was; `S:`
1201    /// spreads them into the items of one array.
1202    Level { u: Box<Verb>, level: i64, spread: bool },
1203    /// J `u b.`: answers questions about u rather than applying it. `0` asks
1204    /// for its three ranks.
1205    Characteristics(Box<Verb>),
1206    /// APL `f⍛g` (before): g's LEFT argument is prepared by f — monad
1207    /// `(f y) g y`, dyad `(f x) g y`. The mirror of [`Verb::Beside`].
1208    Before(Box<Verb>, Box<Verb>),
1209    /// APL `f OP` and `f OP g`: a dfn that mentions `⍺⍺` or `⍵⍵` is an
1210    /// OPERATOR, and this is that operator with its operands supplied. They
1211    /// are bound under those two names for as long as the body runs.
1212    UserDerived { def: Arc<OpDef>, alpha: Operand, omega: Option<Operand> },
1213    /// APL `f⌸` (key, Dyalog): the major cells are grouped by value, and f
1214    /// is applied to each key and the group that shares it. Monadically the
1215    /// group is the positions the key occupies; dyadically it is the items
1216    /// of the right argument at those positions.
1217    KeyPairs(Box<Verb>),
1218    /// J `u/.`: the key dyadically (u over each group of items sharing a
1219    /// key), the oblique monadically (u over each anti-diagonal).
1220    Key(Box<Verb>),
1221    /// J `u;.n`: cut — u over the intervals a fret marks out.
1222    Cut(Box<Verb>, i64),
1223    /// J `u^:v`: v's value at the arguments is the number of applications.
1224    PowerV(Box<Verb>, Box<Verb>),
1225    /// APL `f⍣g`: apply f until `new g old` holds.
1226    PowerUntil(Box<Verb>, Box<Verb>),
1227    /// APL `f[k]`: f along axis k. The axis is brought to the front, f
1228    /// applies to the leading axis, and a result of the argument's own rank
1229    /// has the axis put back where it was.
1230    AlongAxis(Box<Verb>, usize),
1231    /// An explicit definition: a body of sentences run with the arguments
1232    /// bound to names. J's `3 : '…'`, `4 : '…'` and `{{ … }}`, APL's `{…}`
1233    /// and `∇`-defined functions.
1234    Explicit(Arc<crate::ir::ExplicitDef>),
1235    /// J `$:`, APL `∇`: the definition lexically containing the reference,
1236    /// found at run time as the innermost one then running.
1237    SelfRef,
1238    /// A verb named earlier in the program, looked up when it is applied so
1239    /// that a definition can call itself by its own name.
1240    Named(String),
1241    /// J `u :. v`: u, with v declared to be its obverse. The declaration is
1242    /// what `obverse` answers with; applying the verb applies u.
1243    WithObverse(Box<Verb>, Box<Verb>),
1244    /// J `m@.v`: agenda — v's value at the arguments picks which of the
1245    /// gerund's verbs to apply.
1246    Agenda(Vec<Verb>, Box<Verb>),
1247    /// J `u :: v`: adverse — apply u, and if the language refuses it, apply
1248    /// v to the same arguments instead. A gap in libjay is not an error the
1249    /// program may handle, and goes straight through.
1250    Adverse(Box<Verb>, Box<Verb>),
1251    /// J `m H. n`: the generalised hypergeometric function, summed as a
1252    /// series over the numerator parameters m and the denominator ones n.
1253    Hypergeometric { num: Vec<crate::complex::Cx>, den: Vec<crate::complex::Cx> },
1254    /// APL `f∘g` (beside): monad `f (g y)`, dyad `x f (g y)`. g prepares the
1255    /// right argument and the left one arrives untouched, which is what
1256    /// separates it from `⍥` (this crate's [`Verb::Compose`]).
1257    Beside(Box<Verb>, Box<Verb>),
1258    /// APL `f⌺w` (Dyalog's stencil): f applied to the window of `w` cells
1259    /// centred on each cell of y in turn, the edges filled. One size per
1260    /// leading axis; the axes past them travel with the cell.
1261    Stencil(Box<Verb>, Vec<i64>),
1262    /// J `` m`:n `` for the two forms that are not a train: `0` applies
1263    /// every verb of the gerund to the arguments and frames the answers,
1264    /// `3` inserts the verbs between the items of y, cycling through them
1265    /// left to right and folding right to left. `` `:6 `` is a train and is
1266    /// built at parse time, so it never reaches here.
1267    Evoke(Vec<Verb>, i64),
1268    /// J `u . v` and APL `f.g`: the inner product, of which `+/ . *` and
1269    /// `+.×` are the matrix product. Dyadically each cell of x at v's
1270    /// dyadic LEFT rank — 1 where that rank is smaller — meets the whole
1271    /// of y under v, and u folds what comes back. Monadically, which is
1272    /// J's alone, it is the determinant by minors down the first column:
1273    /// `-/ . *` is the determinant proper.
1274    InnerProduct { u: Box<Verb>, v: Box<Verb>, apl: bool },
1275}
1276
1277impl Verb {
1278    /// [monadic, dyadic-left, dyadic-right] ranks governing cell iteration.
1279    pub fn ranks(&self) -> [i64; 3] {
1280        match self {
1281            Verb::Prim(p) => p.ranks,
1282            Verb::Rank(_, r) => *r,
1283            // `x u\ y` and `x u\. y` take one width per application, so the
1284            // left cell is an atom: a list of widths frames the result, as
1285            // in J, and an empty list of them frames nothing.
1286            Verb::Windowed(_, WindowKind::Prefix | WindowKind::Suffix) => {
1287                [RANK_INF, 0, RANK_INF]
1288            }
1289            Verb::Each(..) => [0, 0, 0],
1290            Verb::Fit(v, _) => v.ranks(),
1291            // Amend reads the whole argument, and the rest run their own
1292            // verb over the argument as a whole.
1293            Verb::Amend(_)
1294            | Verb::AmendVerb(_)
1295            | Verb::ShiftFill(_)
1296            | Verb::Level { .. }
1297            | Verb::Characteristics(_)
1298            | Verb::UserDerived { .. }
1299            | Verb::KeyPairs(_)
1300            | Verb::Key(_)
1301            | Verb::Cut(..)
1302            | Verb::PowerV(..)
1303            | Verb::PowerUntil(..)
1304            | Verb::AlongAxis(..) => [RANK_INF, RANK_INF, RANK_INF],
1305            Verb::Memo(v, _) => v.ranks(),
1306            Verb::WithObverse(v, _) | Verb::Adverse(v, _) => v.ranks(),
1307            Verb::Beside(..) => [RANK_INF, RANK_INF, RANK_INF],
1308            // The series is summed for one value at a time.
1309            Verb::Hypergeometric { .. } => [0, 0, 0],
1310            // The determinant is over a table; the dyad reads both
1311            // arguments whole and takes their cells itself.
1312            Verb::InnerProduct { .. } => [2, RANK_INF, RANK_INF],
1313            _ => [RANK_INF, RANK_INF, RANK_INF],
1314        }
1315    }
1316
1317    /// Name for diagnostics, e.g. `+/"1`.
1318    pub fn name(&self) -> String {
1319        match self {
1320            Verb::Prim(p) => p.name.to_string(),
1321            Verb::Rank(v, r) => format!("{}\"{}", v.name(), rank_str(*r)),
1322            Verb::Reduce(v) | Verb::NWise(v) => format!("{}/", v.name()),
1323            Verb::Windowed(v, WindowKind::Suffix) => format!("{}\\.", v.name()),
1324            Verb::Windowed(v, _) => format!("{}\\", v.name()),
1325            Verb::Commute(v) => format!("{}~", v.name()),
1326            Verb::PowerN(v, Power::Converge) => format!("{}^:_", v.name()),
1327            Verb::PowerN(v, Power::Times(n)) => format!("{}^:{n}", v.name()),
1328            Verb::PowerN(v, Power::Each(_)) => format!("{}^:n", v.name()),
1329            Verb::PowerN(v, Power::ConvergeTrace) => format!("{}^:a:", v.name()),
1330            Verb::Fork(f, g, h) => format!("({} {} {})", f.name(), g.name(), h.name()),
1331            Verb::NounFork(_, g, h) => format!("(n {} {})", g.name(), h.name()),
1332            Verb::Hook(f, g) => format!("({} {})", f.name(), g.name()),
1333            Verb::Atop(f, g) => format!("({}@:{})", f.name(), g.name()),
1334            Verb::Compose(f, g) => format!("({}&:{})", f.name(), g.name()),
1335            Verb::BondLeft(_, v) => format!("(n&{})", v.name()),
1336            Verb::BondRight(v, _) => format!("({}&n)", v.name()),
1337            Verb::UnderRavel(v) => format!("({}&.,)", v.name()),
1338            Verb::Each(v, Enclose::Always) => format!("({}&.>)", v.name()),
1339            Verb::Each(v, _) => format!("({}¨)", v.name()),
1340            Verb::Fit(v, n) => format!("{}!.{n}", v.name()),
1341            Verb::Amend(_) => "(m})".to_string(),
1342            Verb::AmendVerb(v) => format!("({}}})", v.name()),
1343            Verb::ShiftFill(_) => "|.!.n".to_string(),
1344            Verb::Characteristics(v) => format!("{} b.", v.name()),
1345            Verb::Before(f, g) => format!("({}⍛{})", f.name(), g.name()),
1346            Verb::KeyPairs(v) => format!("{}⌸", v.name()),
1347            Verb::UserDerived { alpha, omega, .. } => match omega {
1348                Some(g) => format!("({} {{…}} {})", alpha.name(), g.name()),
1349                None => format!("({} {{…}})", alpha.name()),
1350            },
1351            Verb::Memo(v, _) => format!("{} M.", v.name()),
1352            Verb::Level { u, level, spread } => {
1353                format!("{} {} {level}", u.name(), if *spread { "S:" } else { "L:" })
1354            }
1355            Verb::Key(v) => format!("{}/.", v.name()),
1356            Verb::Cut(v, n) => format!("{};.{n}", v.name()),
1357            Verb::PowerV(v, w) => format!("{}^:{}", v.name(), w.name()),
1358            Verb::PowerUntil(v, w) => format!("{}⍣{}", v.name(), w.name()),
1359            Verb::AlongAxis(v, k) => format!("{}[{k}]", v.name()),
1360            Verb::Explicit(d) => d.name.clone(),
1361            Verb::SelfRef => "$:".to_string(),
1362            Verb::Named(n) => n.clone(),
1363            Verb::WithObverse(v, w) => format!("({}:.{})", v.name(), w.name()),
1364            Verb::Adverse(v, w) => format!("({}::{})", v.name(), w.name()),
1365            Verb::Beside(f, g) => format!("({}∘{})", f.name(), g.name()),
1366            Verb::Hypergeometric { num, den } => {
1367                format!("({} H. {})", cx_list(num), cx_list(den))
1368            }
1369            Verb::Agenda(vs, w) => {
1370                let names: Vec<String> = vs.iter().map(Verb::name).collect();
1371                format!("({}@.{})", names.join("`"), w.name())
1372            }
1373            Verb::Evoke(vs, n) => {
1374                let names: Vec<String> = vs.iter().map(Verb::name).collect();
1375                format!("({}`:{n})", names.join("`"))
1376            }
1377            Verb::Stencil(u, w) => {
1378                let sizes: Vec<String> = w.iter().map(i64::to_string).collect();
1379                format!("({}⌺{})", u.name(), sizes.join(" "))
1380            }
1381            Verb::InnerProduct { u, v, .. } => format!("({} . {})", u.name(), v.name()),
1382        }
1383    }
1384
1385    /// True when the verb's meaning depends on the comparison tolerance —
1386    /// the comparisons, the searches that use them, and the two roundings.
1387    /// `u!.n` is only the tolerance conjunction for these; on anything else
1388    /// J's `!.` specifies a fill instead, which is a separate feature.
1389    pub fn uses_tolerance(&self) -> bool {
1390        match self {
1391            Verb::Prim(p) => {
1392                matches!(
1393                    p.monad,
1394                    MonadOp::Scalar(ScalarMonad::Floor)
1395                        | MonadOp::Scalar(ScalarMonad::Ceil)
1396                        | MonadOp::Nub
1397                        | MonadOp::GradeUp { .. }
1398                        | MonadOp::GradeDown { .. }
1399                        | MonadOp::EncodeBits
1400                ) || matches!(
1401                    p.dyad,
1402                    DyadOp::Scalar(
1403                        ScalarDyad::Eq
1404                            | ScalarDyad::Ne
1405                            | ScalarDyad::Lt
1406                            | ScalarDyad::Le
1407                            | ScalarDyad::Gt
1408                            | ScalarDyad::Ge
1409                            | ScalarDyad::Residue
1410                            | ScalarDyad::Gcd
1411                            | ScalarDyad::Lcm
1412                    ) | DyadOp::Match
1413                        | DyadOp::GradeSelect { .. }
1414                        | DyadOp::Encode
1415                        | DyadOp::EncodeApl
1416                        | DyadOp::NotMatch
1417                        | DyadOp::MemberJ
1418                        | DyadOp::MemberApl
1419                        | DyadOp::IndexOf { .. }
1420                        | DyadOp::IndexOfLast { .. }
1421                )
1422            }
1423            Verb::Rank(v, _)
1424            | Verb::Reduce(v)
1425            | Verb::NWise(v)
1426            | Verb::Windowed(v, _)
1427            | Verb::Commute(v)
1428            | Verb::PowerN(v, _)
1429            | Verb::BondLeft(_, v)
1430            | Verb::BondRight(v, _)
1431            | Verb::Each(v, _)
1432            | Verb::UnderRavel(v)
1433            | Verb::Fit(v, _)
1434            | Verb::Key(v)
1435            | Verb::Cut(v, _)
1436            | Verb::AlongAxis(v, _) => v.uses_tolerance(),
1437            Verb::PowerV(v, w) | Verb::PowerUntil(v, w) => {
1438                v.uses_tolerance() || w.uses_tolerance()
1439            }
1440            // An explicit definition's body is a program of its own; `!.`
1441            // has no reach into it.
1442            Verb::Amend(_)
1443            | Verb::AmendVerb(_)
1444            | Verb::ShiftFill(_)
1445            | Verb::Characteristics(_)
1446            | Verb::Explicit(_)
1447            | Verb::SelfRef
1448            | Verb::Named(_)
1449            | Verb::Hypergeometric { .. } => false,
1450            Verb::Memo(v, _) | Verb::Level { u: v, .. } => v.uses_tolerance(),
1451            Verb::WithObverse(v, _) => v.uses_tolerance(),
1452            Verb::Adverse(v, w) | Verb::Beside(v, w) | Verb::Before(v, w) => {
1453                v.uses_tolerance() || w.uses_tolerance()
1454            }
1455            Verb::KeyPairs(v) => v.uses_tolerance(),
1456            Verb::UserDerived { def, alpha, omega } => {
1457                let operand = |o: &Operand| match o {
1458                    Operand::Func(v) => v.uses_tolerance(),
1459                    Operand::Value(_) => false,
1460                };
1461                def.bodies().any(Verb::uses_tolerance)
1462                    || operand(alpha)
1463                    || omega.as_ref().is_some_and(operand)
1464            }
1465            Verb::Agenda(vs, w) => {
1466                w.uses_tolerance() || vs.iter().any(Verb::uses_tolerance)
1467            }
1468            Verb::Evoke(vs, _) => vs.iter().any(Verb::uses_tolerance),
1469            Verb::Stencil(u, _) => u.uses_tolerance(),
1470            Verb::InnerProduct { u, v, .. } => u.uses_tolerance() || v.uses_tolerance(),
1471            Verb::Fork(f, g, h) => {
1472                f.uses_tolerance() || g.uses_tolerance() || h.uses_tolerance()
1473            }
1474            Verb::NounFork(_, g, h)
1475            | Verb::Hook(g, h)
1476            | Verb::Atop(g, h)
1477            | Verb::Compose(g, h) => g.uses_tolerance() || h.uses_tolerance(),
1478        }
1479    }
1480
1481    /// True when applying this verb does nothing beyond producing its
1482    /// result. Output (`echo`, `⎕←`) is the only effect a verb can have, and
1483    /// only a pure verb may have its cells run out of order on several
1484    /// threads. Deliberately conservative: a new effect must be added here.
1485    pub fn is_pure(&self) -> bool {
1486        match self {
1487            // Output and the random source are the two effects a verb can
1488            // have; both fix the order its cells must run in.
1489            Verb::Prim(p) => {
1490                !matches!(
1491                    p.monad,
1492                    MonadOp::Echo | MonadOp::Roll { .. } | MonadOp::ReadStream
1493                ) && !matches!(p.dyad, DyadOp::Deal { .. } | DyadOp::WriteStream)
1494            }
1495            Verb::Rank(v, _)
1496            | Verb::Reduce(v)
1497            | Verb::NWise(v)
1498            | Verb::Windowed(v, _)
1499            | Verb::Commute(v)
1500            | Verb::PowerN(v, _) => v.is_pure(),
1501            Verb::Fork(f, g, h) => f.is_pure() && g.is_pure() && h.is_pure(),
1502            Verb::NounFork(_, g, h)
1503            | Verb::Hook(g, h)
1504            | Verb::Atop(g, h)
1505            | Verb::Compose(g, h) => g.is_pure() && h.is_pure(),
1506            Verb::BondLeft(_, v)
1507            | Verb::BondRight(v, _)
1508            | Verb::Each(v, _)
1509            | Verb::UnderRavel(v)
1510            | Verb::Fit(v, _) => v.is_pure(),
1511            Verb::Key(v) | Verb::Cut(v, _) | Verb::AlongAxis(v, _) => v.is_pure(),
1512            Verb::Hypergeometric { .. } => true,
1513            Verb::PowerV(v, w) | Verb::PowerUntil(v, w) => v.is_pure() && w.is_pure(),
1514            Verb::WithObverse(v, _) => v.is_pure(),
1515            Verb::Adverse(v, w) | Verb::Beside(v, w) | Verb::Before(v, w) => {
1516                v.is_pure() && w.is_pure()
1517            }
1518            Verb::KeyPairs(v) => v.is_pure(),
1519            // The body reads and writes the program's names, exactly as a
1520            // definition called any other way does.
1521            Verb::UserDerived { .. } => false,
1522            Verb::Agenda(vs, w) => w.is_pure() && vs.iter().all(Verb::is_pure),
1523            Verb::Evoke(vs, _) => vs.iter().all(Verb::is_pure),
1524            Verb::Stencil(u, _) => u.is_pure(),
1525            Verb::InnerProduct { u, v, .. } => u.is_pure() && v.is_pure(),
1526            Verb::Amend(_) | Verb::ShiftFill(_) | Verb::Characteristics(_) => true,
1527            Verb::AmendVerb(v) | Verb::Level { u: v, .. } => v.is_pure(),
1528            // A memo answers from its cache, so the verb inside it must be
1529            // pure for the cache to be an optimisation rather than a change
1530            // of meaning; running the cells in any order is then safe too.
1531            Verb::Memo(v, _) => v.is_pure(),
1532            // An explicit definition reads and writes the program's names,
1533            // so its cells can never be run out of order on other threads —
1534            // whatever its body does. `ExplicitDef::pure` records whether
1535            // the body itself has an effect; this is the stronger question.
1536            Verb::Explicit(_) | Verb::SelfRef | Verb::Named(_) => false,
1537        }
1538    }
1539
1540    /// Whether this verb reads a sparse argument in its stored form.
1541    ///
1542    /// The set is small on purpose: `$.` itself, the two verbs that ask
1543    /// about an array rather than about its elements, and the three that
1544    /// draw it. Everything else is handed the dense expansion, which is the
1545    /// same value — the storage kind is not visible in the answer, only in
1546    /// how long it took to get there.
1547    fn monad_reads_sparse(&self) -> bool {
1548        let Verb::Prim(p) = self else { return false };
1549        matches!(
1550            p.monad,
1551            MonadOp::Sparse
1552                | MonadOp::ShapeOf
1553                | MonadOp::Tally
1554                | MonadOp::TypeCode
1555                | MonadOp::Format
1556                | MonadOp::Echo
1557        )
1558    }
1559
1560    /// Whether this verb reads a sparse RIGHT argument in its stored form.
1561    /// A sparse left argument is always expanded: no dyad reads one.
1562    fn dyad_reads_sparse(&self) -> bool {
1563        matches!(self, Verb::Prim(p) if p.dyad == DyadOp::SparseForm)
1564    }
1565
1566    /// Full monadic application including rank/frame machinery.
1567    ///
1568    /// This is one of the two places a column-major argument is dealt with:
1569    /// the verbs that read one natively get it as it lies, and every other
1570    /// verb gets the rows it assumes, materialised once here.
1571    pub fn monad(&self, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1572        let _depth = Nesting::enter(span)?;
1573        // A verb that does not read the stored form gets the array every
1574        // position of it materialised, which is the same value.
1575        let dense;
1576        let y = if y.is_sparse() && !self.monad_reads_sparse() {
1577            dense = y.densified();
1578            &dense
1579        } else {
1580            y
1581        };
1582        if y.is_row_major() {
1583            return self.monad_rows(y, ctx, span);
1584        }
1585        match self.monad_columns(y, ctx, span) {
1586            Some(r) => r,
1587            None => self.monad_rows(&y.to_row_major(), ctx, span),
1588        }
1589    }
1590
1591    /// Monadic application to an argument whose buffer is row-major, which
1592    /// is what everything below assumes.
1593    fn monad_rows(&self, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1594        debug_assert!(y.is_row_major());
1595        match self {
1596            Verb::Prim(p) => {
1597                // Scalar verbs have cell rank 0: the cells are the elements,
1598                // so the whole buffer is one elementwise pass.
1599                if let MonadOp::Scalar(op) = p.monad {
1600                    return scalar_monad(op, y, ctx.cfg, span);
1601                }
1602                // A MIXED SIMPLE array is already simple, so opening it
1603                // changes nothing — and its cells could not be framed back
1604                // into one array if the rank machinery took them apart.
1605                if p.monad == MonadOp::Open && is_mixed_simple(y) {
1606                    return Ok(y.clone());
1607                }
1608                let frame_rank = y.rank() - effective_rank(p.ranks[0], y.rank());
1609                if frame_rank == 0 {
1610                    return monad_op(p, y, ctx, span);
1611                }
1612                let frame = y.shape[..frame_rank].to_vec();
1613                let n: usize = frame.iter().product();
1614                if n == 0 {
1615                    let cell = fill_cell(y, frame_rank, self.is_pure());
1616                    return Ok(empty_frame(&frame, y.dtype(), cell, ctx, |cell, c| {
1617                        monad_op(p, cell, c, span)
1618                    }));
1619                }
1620                let cells = each_cell(n, y.count(), self.is_pure(), ctx, |i, c| {
1621                    monad_op(p, &y.cell_at(frame_rank, i), c, span)
1622                })?;
1623                assemble(&frame, cells, span)
1624            }
1625            Verb::Rank(v, r) => {
1626                let frame_rank = y.rank() - effective_rank(r[0], y.rank());
1627                if frame_rank == 0 {
1628                    // The inner verb applies its own rank machinery to the
1629                    // whole argument; that is what `"` means.
1630                    return v.monad(y, ctx, span);
1631                }
1632                // A reduction over vector cells is every row of the buffer
1633                // folded in place, without an array per cell.
1634                if let Some(a) = reduce_vector_cells(v, y, frame_rank) {
1635                    return Ok(a);
1636                }
1637                let frame = y.shape[..frame_rank].to_vec();
1638                let n: usize = frame.iter().product();
1639                if n == 0 {
1640                    let cell = fill_cell(y, frame_rank, self.is_pure());
1641                    return Ok(empty_frame(&frame, y.dtype(), cell, ctx, |cell, c| {
1642                        v.monad(cell, c, span)
1643                    }));
1644                }
1645                let cells = each_cell(n, y.count(), self.is_pure(), ctx, |i, c| {
1646                    v.monad(&y.cell_at(frame_rank, i), c, span)
1647                })?;
1648                assemble(&frame, cells, span)
1649            }
1650            Verb::Reduce(v) | Verb::NWise(v) => reduce(v, y, ctx, span),
1651            Verb::Windowed(v, kind) => {
1652                runs(v, y, *kind == WindowKind::Suffix, ctx, span)
1653            }
1654            Verb::Commute(v) => v.dyad(y, y, ctx, span),
1655            Verb::PowerN(v, p) => power(v, p.clone(), None, y, ctx, span),
1656            Verb::Fork(f, g, h) => {
1657                let l = f.monad(y, ctx, span)?;
1658                let r = h.monad(y, ctx, span)?;
1659                g.dyad(&l, &r, ctx, span)
1660            }
1661            Verb::NounFork(n, g, h) => {
1662                let r = h.monad(y, ctx, span)?;
1663                g.dyad(n, &r, ctx, span)
1664            }
1665            Verb::Hook(f, g) => {
1666                let r = g.monad(y, ctx, span)?;
1667                f.dyad(y, &r, ctx, span)
1668            }
1669            Verb::Atop(f, g) | Verb::Compose(f, g) => {
1670                let r = g.monad(y, ctx, span)?;
1671                f.monad(&r, ctx, span)
1672            }
1673            Verb::BondLeft(m, v) => v.dyad(m, y, ctx, span),
1674            Verb::BondRight(v, n) => v.dyad(y, n, ctx, span),
1675            Verb::Each(u, rule) => {
1676                let n = y.count();
1677                let cells = each_cell(n, n, self.is_pure(), ctx, |i, c| {
1678                    let opened = open_cell(&atom(y, i));
1679                    Ok(enclose(&u.monad(&opened, c, span)?, *rule))
1680                })?;
1681                assemble(&y.shape, cells, span)
1682            }
1683            // `u&., y`: the shape is put back afterwards, so a ravel that
1684            // says nothing about where it came from still has an inverse
1685            // for as long as this one argument is in hand.
1686            Verb::UnderRavel(u) => {
1687                let flat = Array::new(vec![y.count()], y.data.clone());
1688                let r = u.monad(&flat, ctx, span)?;
1689                let shape = Array::from_i64(y.shape.iter().map(|&n| n as i64).collect());
1690                reshape(&shape, &r, false, false, ctx.cfg.near(), span)
1691            }
1692            Verb::Fit(v, n) => {
1693                let tol = Tol { ct: *n, ..ctx.cfg.tol };
1694                ctx.with_tol(tol, |c| v.monad(y, c, span))
1695            }
1696            // `m} y` with one index is J's item selection.
1697            Verb::Amend(m) => {
1698                if m.rank() != 0 || y.rank() > 1 {
1699                    return Err(Error::new(
1700                        ErrorKind::Rank,
1701                        "selecting with m} takes one index into a list",
1702                        Some(span),
1703                    ));
1704                }
1705                from_index(m, y, ctx.cfg.near(), span)
1706            }
1707            // `u} y` computes the indices first: it is `(u y)} y`.
1708            Verb::AmendVerb(u) => {
1709                let m = u.monad(y, ctx, span)?;
1710                Verb::Amend(m).monad(y, ctx, span)
1711            }
1712            // The monad shifts by one, the fill taking the place the
1713            // first item left: `|.!.f y` is `_1 |.!.f y`.
1714            Verb::ShiftFill(fill) => {
1715                shift_fill(&Array::scalar_i64(-1), y, fill, ctx.cfg.near(), span)
1716            }
1717            Verb::Memo(u, cache) => memoised(u, cache, None, y, ctx, span),
1718            Verb::Characteristics(u) => characteristics(u, y, span),
1719            Verb::Before(f, g) => {
1720                let l = f.monad(y, ctx, span)?;
1721                g.dyad(&l, y, ctx, span)
1722            }
1723            Verb::KeyPairs(u) => key_pairs(u, y, None, ctx, span),
1724            Verb::UserDerived { def, alpha, omega } => {
1725                let body = def.pick(alpha, omega.as_ref())?.clone();
1726                with_operands(alpha, omega.as_ref(), ctx, |c| body.monad(y, c, span))
1727            }
1728            Verb::Level { u, level, spread } => {
1729                at_level(u, *level, *spread, y, ctx, span)
1730            }
1731            Verb::Key(u) => oblique(u, y, ctx, span),
1732            Verb::Cut(u, n) => cut(u, None, y, *n, ctx, span),
1733            Verb::PowerV(u, v) => power_v(u, v, None, y, ctx, span),
1734            Verb::PowerUntil(u, v) => power_until(u, v, y, ctx, span),
1735            Verb::AlongAxis(u, k) => along_axis(u, None, y, *k, ctx, span),
1736            Verb::Explicit(d) => crate::ir::call_explicit(d, None, y, ctx, span),
1737            Verb::SelfRef => {
1738                let d = self_ref(ctx, span)?;
1739                crate::ir::call_explicit(&d, None, y, ctx, span)
1740            }
1741            Verb::Named(n) => named_verb(ctx, n, span)?.monad(y, ctx, span),
1742            Verb::WithObverse(v, _) => v.monad(y, ctx, span),
1743            Verb::Adverse(v, w) => match v.monad(y, ctx, span) {
1744                Err(e) if e.kind != ErrorKind::NotYet => w.monad(y, ctx, span),
1745                other => other,
1746            },
1747            Verb::Beside(f, g) => {
1748                let r = g.monad(y, ctx, span)?;
1749                f.monad(&r, ctx, span)
1750            }
1751            Verb::Hypergeometric { num, den } => hypergeometric(num, den, y, span),
1752            Verb::Agenda(vs, w) => {
1753                agenda_pick(vs, w, None, y, ctx, span)?.monad(y, ctx, span)
1754            }
1755            Verb::Evoke(vs, n) => evoke(vs, *n, None, y, ctx, span),
1756            Verb::Stencil(u, w) => stencil(u, w, y, ctx, span),
1757            Verb::InnerProduct { u, v, apl } => determinant(u, v, *apl, y, ctx, span),
1758        }
1759    }
1760
1761    /// Monadic application to a column-major argument, for the verbs that
1762    /// read one where it lies. None means this verb is not one of them and
1763    /// the caller must materialise the rows first.
1764    ///
1765    /// Every arm here either reads the buffer in an order it chooses (the
1766    /// folds), reads it elementwise (order cannot matter), or answers from
1767    /// the shape alone. Nothing else may be added without the same argument
1768    /// holding for it.
1769    fn monad_columns(&self, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Option<Result<Array>> {
1770        debug_assert!(!y.is_row_major());
1771        match self {
1772            Verb::Prim(p) => match p.monad {
1773                // Elementwise: every element is read and written where it
1774                // lies, so the answer carries the argument's own layout.
1775                MonadOp::Scalar(op) => Some(scalar_monad(op, y, ctx.cfg, span)),
1776                // The shape is the logical one whatever the buffer does.
1777                MonadOp::ShapeOf | MonadOp::Tally => Some(monad_op(p, y, ctx, span)),
1778                // Reversing the axes of a column-major buffer is reading the
1779                // same buffer as a row-major one of the reversed shape: the
1780                // transpose that costs nothing.
1781                MonadOp::TransposeAxes => Some(Ok(transpose_axes(y))),
1782                _ => None,
1783            },
1784            // `u/ y` folds the leading axis, and in this layout the leading
1785            // axis is what each contiguous run holds.
1786            Verb::Reduce(v) | Verb::NWise(v) => reduce_columns(v, y).map(Ok),
1787            // `u/"1 y` folds each row across the columns, which is one
1788            // elementwise pass per column and no transpose at all.
1789            Verb::Rank(v, r) => {
1790                if y.rank() != effective_rank(r[0], y.rank()) + 1 {
1791                    return None;
1792                }
1793                reduce_rows_columns(v, y).map(Ok)
1794            }
1795            _ => None,
1796        }
1797    }
1798
1799    /// Full dyadic application including rank/frame/agreement machinery.
1800    ///
1801    /// The other place a column-major argument is dealt with: an
1802    /// elementwise verb over arguments that agree exactly reads the buffers
1803    /// as they lie and keeps the layout, and everything else is given rows.
1804    pub fn dyad(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1805        let _depth = Nesting::enter(span)?;
1806        // As in `monad`: only `x $. y` reads a sparse argument as it lies,
1807        // and even there the left one names a form and is always dense.
1808        let (dense_x, dense_y);
1809        let x = if x.is_sparse() {
1810            dense_x = x.densified();
1811            &dense_x
1812        } else {
1813            x
1814        };
1815        let y = if y.is_sparse() && !self.dyad_reads_sparse() {
1816            dense_y = y.densified();
1817            &dense_y
1818        } else {
1819            y
1820        };
1821        if x.is_row_major() && y.is_row_major() {
1822            return self.dyad_rows(x, y, ctx, span);
1823        }
1824        if let Some(layout) = self.elementwise_layout(x, y) {
1825            return Ok(self.dyad_rows(x, y, ctx, span)?.with_layout(layout));
1826        }
1827        self.dyad_rows(&x.to_row_major(), &y.to_row_major(), ctx, span)
1828    }
1829
1830    /// The layout a dyadic result keeps when its arguments are not both
1831    /// row-major: an elementwise primitive over a scalar and an array, or
1832    /// over two arrays of one shape and one layout, computes each element
1833    /// from the elements at its own index and nothing else.
1834    fn elementwise_layout(&self, x: &Array, y: &Array) -> Option<Layout> {
1835        let Verb::Prim(p) = self else { return None };
1836        if !matches!(p.dyad, DyadOp::Scalar(_)) {
1837            return None;
1838        }
1839        if x.rank() == 0 {
1840            return Some(y.layout());
1841        }
1842        if y.rank() == 0 {
1843            return Some(x.layout());
1844        }
1845        (x.shape == y.shape && x.layout() == y.layout()).then(|| x.layout())
1846    }
1847
1848    /// Dyadic application proper: reached with row-major arguments, or with
1849    /// arguments whose layout the verb above has established it is
1850    /// indifferent to.
1851    fn dyad_rows(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1852        match self {
1853            Verb::Prim(_) | Verb::Rank(_, _) | Verb::Each(..) => {
1854                self.dyad_ranked(x, y, ctx, span)
1855            }
1856            // `x u\ y` and `x u\. y` need the frame machinery: their left
1857            // cell is an atom.
1858            Verb::Windowed(_, WindowKind::Prefix | WindowKind::Suffix) => {
1859                self.dyad_ranked(x, y, ctx, span)
1860            }
1861            Verb::Windowed(_, WindowKind::Scan) => {
1862                Err(Error::not_yet("dyadic scan (x f\\ y)", span))
1863            }
1864            Verb::Commute(v) => v.dyad(y, x, ctx, span),
1865            Verb::PowerN(v, p) => power(v, p.clone(), Some(x), y, ctx, span),
1866            // `x u/ y` is the table: every cell of x against every cell of y.
1867            Verb::Reduce(v) => table(v, x, y, ctx, span),
1868            // `n f/ y` is APL's n-wise reduction, a different function.
1869            Verb::NWise(v) => nwise(v, x, y, ctx, span),
1870            Verb::Fork(f, g, h) => {
1871                let l = f.dyad(x, y, ctx, span)?;
1872                let r = h.dyad(x, y, ctx, span)?;
1873                g.dyad(&l, &r, ctx, span)
1874            }
1875            Verb::NounFork(n, g, h) => {
1876                let r = h.dyad(x, y, ctx, span)?;
1877                g.dyad(n, &r, ctx, span)
1878            }
1879            Verb::Hook(f, g) => {
1880                let r = g.monad(y, ctx, span)?;
1881                f.dyad(x, &r, ctx, span)
1882            }
1883            Verb::Atop(f, g) => {
1884                let r = g.dyad(x, y, ctx, span)?;
1885                f.monad(&r, ctx, span)
1886            }
1887            Verb::Compose(f, g) => {
1888                let l = g.monad(x, ctx, span)?;
1889                let r = g.monad(y, ctx, span)?;
1890                f.dyad(&l, &r, ctx, span)
1891            }
1892            Verb::Fit(v, n) => {
1893                let tol = Tol { ct: *n, ..ctx.cfg.tol };
1894                ctx.with_tol(tol, |c| v.dyad(x, y, c, span))
1895            }
1896            Verb::Amend(m) => amend(m, x, y, ctx.cfg.near(), span),
1897            // `x u} y` is `x (x u y)} y`: u names the places to amend.
1898            Verb::AmendVerb(u) => {
1899                let m = u.dyad(x, y, ctx, span)?;
1900                amend(&m, x, y, ctx.cfg.near(), span)
1901            }
1902            Verb::ShiftFill(fill) => shift_fill(x, y, fill, ctx.cfg.near(), span),
1903            Verb::Memo(u, cache) => memoised(u, cache, Some(x), y, ctx, span),
1904            Verb::Characteristics(_) => {
1905                Err(Error::domain("u b. has no dyadic meaning", span))
1906            }
1907            Verb::Before(f, g) => {
1908                let l = f.monad(x, ctx, span)?;
1909                g.dyad(&l, y, ctx, span)
1910            }
1911            Verb::KeyPairs(u) => key_pairs(u, x, Some(y), ctx, span),
1912            Verb::UserDerived { def, alpha, omega } => {
1913                let body = def.pick(alpha, omega.as_ref())?.clone();
1914                with_operands(alpha, omega.as_ref(), ctx, |c| body.dyad(x, y, c, span))
1915            }
1916            Verb::Level { u, level, spread } => {
1917                at_level_dyad(u, *level, *spread, x, y, ctx, span)
1918            }
1919            Verb::Key(u) => key(u, x, y, ctx, span),
1920            Verb::Cut(u, n) => cut(u, Some(x), y, *n, ctx, span),
1921            Verb::PowerV(u, v) => power_v(u, v, Some(x), y, ctx, span),
1922            Verb::PowerUntil(..) => {
1923                Err(Error::not_yet("dyadic power with a function operand (x f⍣g y)", span))
1924            }
1925            Verb::AlongAxis(u, k) => along_axis(u, Some(x), y, *k, ctx, span),
1926            Verb::Explicit(d) => crate::ir::call_explicit(d, Some(x), y, ctx, span),
1927            Verb::SelfRef => {
1928                let d = self_ref(ctx, span)?;
1929                crate::ir::call_explicit(&d, Some(x), y, ctx, span)
1930            }
1931            Verb::Named(n) => named_verb(ctx, n, span)?.dyad(x, y, ctx, span),
1932            Verb::WithObverse(v, _) => v.dyad(x, y, ctx, span),
1933            Verb::Adverse(v, w) => match v.dyad(x, y, ctx, span) {
1934                Err(e) if e.kind != ErrorKind::NotYet => w.dyad(x, y, ctx, span),
1935                other => other,
1936            },
1937            Verb::Beside(f, g) => {
1938                let r = g.monad(y, ctx, span)?;
1939                f.dyad(x, &r, ctx, span)
1940            }
1941            Verb::Hypergeometric { .. } => {
1942                Err(Error::domain("m H. n has no dyadic meaning", span))
1943            }
1944            Verb::Agenda(vs, w) => {
1945                agenda_pick(vs, w, Some(x), y, ctx, span)?.dyad(x, y, ctx, span)
1946            }
1947            Verb::Evoke(vs, n) => evoke(vs, *n, Some(x), y, ctx, span),
1948            Verb::InnerProduct { u, v, apl } => inner_product(u, v, *apl, x, y, ctx, span),
1949            Verb::Stencil(..) => {
1950                Err(Error::domain("f⌺w has no dyadic meaning", span))
1951            }
1952            // J gives a bond, and under-ravel, one valence only.
1953            Verb::BondLeft(..) | Verb::BondRight(..) | Verb::UnderRavel(_) => {
1954                Err(Error::domain(format!("{} has no dyadic meaning", self.name()), span))
1955            }
1956        }
1957    }
1958
1959    /// Dyadic application for the verbs that carry cell ranks.
1960    fn dyad_ranked(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1961        let ranks = self.ranks();
1962        let er_l = effective_rank(ranks[1], x.rank());
1963        let er_r = effective_rank(ranks[2], y.rank());
1964        if er_l == 0 && er_r == 0 {
1965            // Both cells are elements: run the flat elementwise path instead
1966            // of materialising one Array per element.
1967            if let Some(op) = self.scalar_dyad_op() {
1968                return scalar_dyad(op, x, y, ctx.cfg, span);
1969            }
1970        }
1971        let fxl = x.rank() - er_l;
1972        let fyl = y.rank() - er_r;
1973        let p = agree(&x.shape[..fxl], &y.shape[..fyl], &x.shape, &y.shape, ctx.cfg.agreement, span)?;
1974        if p.frame.is_empty() {
1975            return self.dyad_cell(x, y, ctx, span);
1976        }
1977        if p.n == 0 {
1978            let right = fill_cell(y, fyl, self.is_pure());
1979            let cell = fill_cell(x, fxl, self.is_pure()).filter(|_| right.is_some());
1980            return Ok(empty_frame(&p.frame, y.dtype(), cell, ctx, |left, c| {
1981                let right = right.as_ref().expect("a left fill cell comes with a right one");
1982                self.dyad_cell(left, right, c, span)
1983            }));
1984        }
1985        let work = x.count().max(y.count());
1986        let cells = each_cell(p.n, work, self.is_pure(), ctx, |i, c| {
1987            let xc = x.cell_at(fxl, i / p.x_div);
1988            let yc = y.cell_at(fyl, i / p.y_div);
1989            self.dyad_cell(&xc, &yc, c, span)
1990        })?;
1991        assemble(&p.frame, cells, span)
1992    }
1993
1994    /// The meaning applied to one pair of cells by `dyad_ranked`.
1995    fn dyad_cell(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1996        match self {
1997            // The one dyad that writes: it needs the sink, and the
1998            // dispatcher below it is the pure half of the evaluator.
1999            Verb::Prim(p) if p.dyad == DyadOp::WriteStream => {
2000                stream_number(y, 2, "1!:2 writes", span)?;
2001                (ctx.out)(&format!("{}\n", crate::fmt::format_array(x, &ctx.cfg.fmt)));
2002                Ok(x.clone())
2003            }
2004            Verb::Prim(p) => dyad_op(p, x, y, ctx.cfg, span),
2005            Verb::Rank(v, _) => v.dyad(x, y, ctx, span),
2006            // The infix takes runs of x items; the outfix leaves them out.
2007            Verb::Windowed(v, WindowKind::Suffix) => outfix(v, x, y, ctx, span),
2008            Verb::Windowed(v, _) => infix(v, x, y, ctx, span),
2009            Verb::Each(u, rule) => {
2010                let r = u.dyad(&open_cell(x), &open_cell(y), ctx, span)?;
2011                Ok(enclose(&r, *rule))
2012            }
2013            _ => Err(Error::internal("dyad_cell on a verb without cell ranks")),
2014        }
2015    }
2016
2017    /// The elementwise dyadic operation this verb performs on element cells,
2018    /// if it performs one.
2019    fn scalar_dyad_op(&self) -> Option<ScalarDyad> {
2020        match self {
2021            Verb::Prim(p) => match p.dyad {
2022                DyadOp::Scalar(op) => Some(op),
2023                _ => None,
2024            },
2025            Verb::Rank(v, _) => v.scalar_dyad_op(),
2026            _ => None,
2027        }
2028    }
2029}
2030
2031/// Effective cell rank: nonnegative rank clamps to the argument's rank;
2032/// negative rank means "leave |r| frame axes" (at least rank 0 cells).
2033pub fn effective_rank(r: i64, arg_rank: usize) -> usize {
2034    if r >= 0 {
2035        (r as usize).min(arg_rank)
2036    } else {
2037        arg_rank.saturating_sub(r.unsigned_abs() as usize)
2038    }
2039}
2040
2041/// Apply `f` to the `n` cells of a frame, in index order.
2042///
2043/// Cells are independent, so a pure verb runs them on several threads and
2044/// the results are framed afterwards; an impure one keeps the caller's
2045/// context, and with it the order its output appears in. `work` is the
2046/// number of elements the whole application touches, which decides whether
2047/// splitting is worth it. Either way the first failing cell in index order
2048/// supplies the error.
2049/// The definition `$:` or `∇` names: the innermost one now running.
2050fn self_ref(ctx: &Ctx<'_>, span: Span) -> Result<Arc<crate::ir::ExplicitDef>> {
2051    ctx.env.current_def().ok_or_else(|| {
2052        Error::new(
2053            ErrorKind::Value,
2054            "self-reference outside an explicit definition",
2055            Some(span),
2056        )
2057    })
2058}
2059
2060/// A verb the program named earlier, resolved when it is applied.
2061fn named_verb(ctx: &Ctx<'_>, name: &str, span: Span) -> Result<Verb> {
2062    ctx.env.verb(name).cloned().ok_or_else(|| {
2063        Error::new(ErrorKind::Value, format!("undefined verb: {name}"), Some(span))
2064    })
2065}
2066
2067fn each_cell<F>(
2068    n: usize,
2069    work: usize,
2070    pure: bool,
2071    ctx: &mut Ctx<'_>,
2072    f: F,
2073) -> Result<Vec<Array>>
2074where
2075    F: Fn(usize, &mut Ctx<'_>) -> Result<Array> + Sync + Send,
2076{
2077    if pure && n > 1 && par::worth_it(work) {
2078        let cfg = ctx.cfg;
2079        return par::map_indexed(n, |i| cfg.pure(|c| f(i, c))).into_iter().collect();
2080    }
2081    (0..n).map(|i| f(i, ctx)).collect()
2082}
2083
2084// ---------------------------------------------------------------- naming
2085
2086fn one_rank(r: i64) -> String {
2087    if r == RANK_INF { "_".to_string() } else { r.to_string() }
2088}
2089
2090/// The rank list as `"` writes it: one number when all three agree,
2091/// otherwise monadic, dyadic-left, dyadic-right.
2092fn rank_str(r: [i64; 3]) -> String {
2093    if r[0] == r[1] && r[1] == r[2] {
2094        one_rank(r[0])
2095    } else {
2096        format!("{} {} {}", one_rank(r[0]), one_rank(r[1]), one_rank(r[2]))
2097    }
2098}
2099
2100/// A shape as it appears in diagnostics.
2101fn show_shape(shape: &[usize]) -> String {
2102    if shape.is_empty() {
2103        return "(scalar)".to_string();
2104    }
2105    shape.iter().map(|n| n.to_string()).collect::<Vec<_>>().join(" ")
2106}
2107
2108// ------------------------------------------------------------- indexing
2109
2110/// Row-major strides for `shape`.
2111fn strides(shape: &[usize]) -> Vec<usize> {
2112    let mut s = vec![1usize; shape.len()];
2113    for k in (0..shape.len().saturating_sub(1)).rev() {
2114        s[k] = s[k + 1] * shape[k + 1];
2115    }
2116    s
2117}
2118
2119/// Step `coord` to the next position in row-major order within `shape`.
2120fn odometer(coord: &mut [usize], shape: &[usize]) {
2121    for k in (0..coord.len()).rev() {
2122        coord[k] += 1;
2123        if coord[k] < shape[k] {
2124            return;
2125        }
2126        coord[k] = 0;
2127    }
2128}
2129
2130/// Append element `i` of `src` to `dst`. Both must have the same dtype.
2131fn push_elem(dst: &mut Data, src: &Data, i: usize) {
2132    match (dst, src) {
2133        (Data::Bool(a), Data::Bool(b)) => a.push(b[i]),
2134        (Data::I64(a), Data::I64(b)) => a.push(b[i]),
2135        (Data::Ext(a), Data::Ext(b)) => a.push(b[i].clone()),
2136        (Data::Rat(a), Data::Rat(b)) => a.push(b[i].clone()),
2137        (Data::F64(a), Data::F64(b)) => a.push(b[i]),
2138        (Data::Complex(a), Data::Complex(b)) => a.push(b[i]),
2139        (Data::Char(a), Data::Char(b)) => a.push(b[i]),
2140        (Data::Symbol(a), Data::Symbol(b)) => a.push(b[i]),
2141        (Data::Box(a), Data::Box(b)) => a.push(b[i].clone()),
2142        _ => debug_assert!(false, "push_elem across dtypes"),
2143    }
2144}
2145
2146/// `n` fill elements of the given type.
2147fn fill_data(dtype: DType, n: usize) -> Data {
2148    let mut d = Data::empty(dtype);
2149    for _ in 0..n {
2150        d.push_fill();
2151    }
2152    d
2153}
2154
2155/// The largest fill cell worth building to learn a shape from.
2156const FILL_CELL_LIMIT: usize = 1 << 20;
2157
2158/// A cell to learn a shape from where the application had none to run.
2159///
2160/// An argument whose own frame is not empty still HAS cells — a dyad frames
2161/// over both arguments, and only one of them need be the empty one — so its
2162/// first cell stands in as it is, and only an argument with no cells at all
2163/// is stood in for by a cell of fills.
2164///
2165/// `None` where the verb is not pure — running it to learn a shape would be
2166/// running it for its effects — or where the cell is too large to be worth
2167/// building.
2168fn fill_cell(y: &Array, frame_rank: usize, pure: bool) -> Option<Array> {
2169    if !pure {
2170        return None;
2171    }
2172    if y.shape[..frame_rank].iter().all(|&d| d != 0) {
2173        return Some(y.cell_at(frame_rank, 0));
2174    }
2175    let shape = y.shape[frame_rank..].to_vec();
2176    let n: usize = shape.iter().product();
2177    if n > FILL_CELL_LIMIT {
2178        return None;
2179    }
2180    // A nested argument that remembers its items fills with the prototype,
2181    // so that mixing an empty keeps the axes its items had.
2182    let fill = y.proto().cloned();
2183    let mut data = Data::empty(y.dtype());
2184    for _ in 0..n {
2185        push_gap(&mut data, &fill);
2186    }
2187    Some(Array::new(shape, data))
2188}
2189
2190/// The result of a cell-by-cell application that has no cells to frame.
2191///
2192/// The frame says how many cells there would have been, not what shape one
2193/// would have had, so an empty of the frame's shape alone drops whatever
2194/// axes the cells carried: `(,"1) i. 0 3` is a 0 by 3 table, not a list.
2195/// The missing axes come from running the verb once on a cell of fills and
2196/// keeping the shape of the answer, which is J's own rule. A verb that
2197/// refuses the fill cell, or a cell there was no point building, leaves the
2198/// frame standing on its own, holding the argument's type.
2199fn empty_frame(
2200    frame: &[usize],
2201    dtype: DType,
2202    cell: Option<Array>,
2203    ctx: &mut Ctx<'_>,
2204    run: impl FnOnce(&Array, &mut Ctx<'_>) -> Result<Array>,
2205) -> Array {
2206    let mut shape = frame.to_vec();
2207    if let Some(cell) = cell
2208        && let Ok(answer) = run(&cell, ctx)
2209    {
2210        shape.extend_from_slice(&answer.shape);
2211        return Array::new(shape, Data::empty(answer.dtype()));
2212    }
2213    Array::new(shape, Data::empty(dtype))
2214}
2215
2216// ------------------------------------------------------------ agreement
2217
2218/// How result cells map back to argument cells: result cell `i` uses left
2219/// cell `i / x_div` and right cell `i / y_div`.
2220struct Pairing {
2221    frame: Vec<usize>,
2222    n: usize,
2223    x_div: usize,
2224    y_div: usize,
2225}
2226
2227fn frame_mismatch(
2228    xs: &[usize],
2229    ys: &[usize],
2230    fx: &[usize],
2231    fy: &[usize],
2232    axis: usize,
2233    span: Span,
2234) -> Error {
2235    // 1-D against 1-D is a length error in both languages; anything else is
2236    // reported as a shape error.
2237    let kind = if fx.len() == 1 && fy.len() == 1 { ErrorKind::Length } else { ErrorKind::Shape };
2238    let note = if axis < fx.len() && axis < fy.len() {
2239        format!("frames first differ at axis {axis}: {} vs {}", fx[axis], fy[axis])
2240    } else {
2241        format!(
2242            "frames have different numbers of axes: {} vs {}, diverging at axis {axis}",
2243            fx.len(),
2244            fy.len()
2245        )
2246    };
2247    Error::new(
2248        kind,
2249        format!(
2250            "arguments do not agree: left shape {}, right shape {}",
2251            show_shape(xs),
2252            show_shape(ys)
2253        ),
2254        Some(span),
2255    )
2256    .note(note)
2257}
2258
2259/// Check frame agreement and build the cell pairing. `xs`/`ys` are the full
2260/// argument shapes, used only for diagnostics.
2261fn agree(
2262    fx: &[usize],
2263    fy: &[usize],
2264    xs: &[usize],
2265    ys: &[usize],
2266    mode: Agreement,
2267    span: Span,
2268) -> Result<Pairing> {
2269    let common = fx.len().min(fy.len());
2270    match mode {
2271        Agreement::LeadingPrefix => {
2272            for i in 0..common {
2273                if fx[i] != fy[i] {
2274                    return Err(frame_mismatch(xs, ys, fx, fy, i, span));
2275                }
2276            }
2277            let (long, short) = if fx.len() >= fy.len() { (fx, fy) } else { (fy, fx) };
2278            let n: usize = long.iter().product();
2279            let surplus: usize = long[short.len()..].iter().product();
2280            let (x_div, y_div) =
2281                if fx.len() >= fy.len() { (1, surplus.max(1)) } else { (surplus.max(1), 1) };
2282            Ok(Pairing { frame: long.to_vec(), n, x_div, y_div })
2283        }
2284        Agreement::ExactOrScalar => {
2285            if fx == fy {
2286                let n: usize = fx.iter().product();
2287                return Ok(Pairing { frame: fx.to_vec(), n, x_div: 1, y_div: 1 });
2288            }
2289            // APL extends any frame of ONE cell, whatever its rank, not
2290            // only a scalar one: `(1 1⍴5)+1 2 3` is `6 7 8`. A rank-0 frame
2291            // — a true scalar — always gives way to the other side, and
2292            // between two one-cell frames that are not scalars the answer
2293            // keeps the RIGHT one: `(1 1⍴5)+,3` is a one-item VECTOR, while
2294            // `(1 1⍴5)+3` keeps the 1 by 1 table.
2295            let one = |f: &[usize]| f.iter().product::<usize>() == 1;
2296            if fx.is_empty() || (one(fx) && !fy.is_empty()) {
2297                let n: usize = fy.iter().product();
2298                return Ok(Pairing { frame: fy.to_vec(), n, x_div: n.max(1), y_div: 1 });
2299            }
2300            if fy.is_empty() || one(fy) {
2301                let n: usize = fx.iter().product();
2302                return Ok(Pairing { frame: fx.to_vec(), n, x_div: 1, y_div: n.max(1) });
2303            }
2304            let axis = (0..common).find(|&i| fx[i] != fy[i]).unwrap_or(common);
2305            Err(frame_mismatch(xs, ys, fx, fy, axis, span))
2306        }
2307    }
2308}
2309
2310// ------------------------------------------------------------- assembly
2311
2312/// Frame results that need not share a depth, which is how APL collects the
2313/// values of an application between items: `,\1 2 3` puts the simple scalar
2314/// `1` beside two enclosed vectors. A simple scalar cannot be nested, so it
2315/// is enclosed here to take its place among the others; anything already
2316/// alike goes straight to [`assemble`]. J refuses such a mixture instead,
2317/// and reaches [`assemble`] directly.
2318fn assemble_items(frame: &[usize], mut cells: Vec<Array>, span: Span) -> Result<Array> {
2319    let boxes = cells.iter().filter(|c| c.dtype() == DType::Box).count();
2320    if boxes > 0 && boxes < cells.len() {
2321        for c in &mut cells {
2322            if c.dtype() != DType::Box {
2323                *c = boxed_elements(c);
2324            }
2325        }
2326    }
2327    assemble(frame, cells, span)
2328}
2329
2330/// The same array with every element held as its own value, so that it can
2331/// be framed beside cells whose elements are nested.
2332fn boxed_elements(a: &Array) -> Array {
2333    let row = a.to_row_major();
2334    let held: Vec<Array> = (0..row.count()).map(|i| atom(&row, i)).collect();
2335    Array::new(row.shape.clone(), Data::Box(held.into()))
2336}
2337
2338/// Frame the results of a cell-by-cell application into one array.
2339///
2340/// The cells arrive as their verb left them, and a verb may leave a
2341/// column-major one — `|:` flips the layout flag rather than moving the
2342/// buffer. Framing splices the buffers end to end, so every cell is made
2343/// row-major first; an already row-major one costs a refcount bump.
2344fn assemble(frame: &[usize], cells: Vec<Array>, span: Span) -> Result<Array> {
2345    if cells.is_empty() {
2346        // Nothing to take a cell shape from. J runs the verb on a fill cell
2347        // to learn the shape; we yield an empty array of the frame's shape.
2348        return Ok(Array::new(frame.to_vec(), Data::empty(DType::I64)));
2349    }
2350    let cells: Vec<Array> =
2351        if cells.iter().all(Array::is_row_major) {
2352            cells
2353        } else {
2354            cells.iter().map(Array::to_row_major).collect()
2355        };
2356    // A cell with no elements takes the type of the cells that have some,
2357    // rather than clashing with them: `(0$'a') ,: 1 2 3` frames an empty
2358    // character list beside a numeric one and answers two numeric rows.
2359    // Where every cell is empty the wider container wins — a box over a
2360    // character, a character over a number.
2361    let mut dt = cells.iter().find(|c| c.count() > 0).unwrap_or(&cells[0]).dtype();
2362    for c in &cells {
2363        if c.count() == 0 {
2364            continue;
2365        }
2366        dt = DType::promote(dt, c.dtype()).ok_or_else(|| {
2367            let boxed = dt == DType::Box || c.dtype() == DType::Box;
2368            let what = if boxed {
2369                "cannot frame boxed and unboxed results into one array"
2370            } else {
2371                "cannot frame character and numeric results into one array"
2372            };
2373            Error::new(ErrorKind::Type, what, Some(span))
2374        })?;
2375    }
2376    if cells.iter().all(|c| c.count() == 0) {
2377        for c in &cells {
2378            dt = DType::promote(dt, c.dtype()).unwrap_or(match (dt, c.dtype()) {
2379                (DType::Box, _) | (_, DType::Box) => DType::Box,
2380                _ => DType::Char,
2381            });
2382        }
2383    }
2384    let widen = |c: &Array| -> Result<Data> {
2385        if c.count() == 0 {
2386            return Ok(Data::empty(dt));
2387        }
2388        c.data.cast(dt).ok_or_else(|| Error::internal("unsupported widening while framing"))
2389    };
2390
2391    if cells[1..].iter().all(|c| c.shape == cells[0].shape) {
2392        let mut data = Data::empty(dt);
2393        for c in &cells {
2394            if c.dtype() == dt {
2395                data.extend_from(&c.data);
2396            } else {
2397                data.extend_from(&widen(c)?);
2398            }
2399        }
2400        let mut shape = frame.to_vec();
2401        shape.extend_from_slice(&cells[0].shape);
2402        return Ok(Array::new(shape, data));
2403    }
2404
2405    // Unequal cell shapes: pad every cell out to the per-axis maximum,
2406    // aligning lower-rank cells at the trailing axes.
2407    let crank = cells.iter().map(|c| c.rank()).max().unwrap_or(0);
2408    let padded: Vec<Vec<usize>> = cells
2409        .iter()
2410        .map(|c| {
2411            let mut s = vec![1usize; crank - c.rank()];
2412            s.extend_from_slice(&c.shape);
2413            s
2414        })
2415        .collect();
2416    let mut common = vec![0usize; crank];
2417    for s in &padded {
2418        for k in 0..crank {
2419            common[k] = common[k].max(s[k]);
2420        }
2421    }
2422    let cell_n: usize = common.iter().product();
2423    let mut data = Data::empty(dt);
2424    for (c, ps) in cells.iter().zip(&padded) {
2425        let cd = if c.dtype() == dt { c.data.clone() } else { widen(c)? };
2426        let st = strides(ps);
2427        let mut coord = vec![0usize; crank];
2428        for _ in 0..cell_n {
2429            let mut idx = 0usize;
2430            let mut inside = true;
2431            for k in 0..crank {
2432                if coord[k] >= ps[k] {
2433                    inside = false;
2434                    break;
2435                }
2436                idx += coord[k] * st[k];
2437            }
2438            if inside {
2439                push_elem(&mut data, &cd, idx);
2440            } else {
2441                data.push_fill();
2442            }
2443            odometer(&mut coord, &common);
2444        }
2445    }
2446    let mut shape = frame.to_vec();
2447    shape.extend_from_slice(&common);
2448    Ok(Array::new(shape, data))
2449}
2450
2451// ------------------------------------------------------------------ boxes
2452
2453/// Element `i` of `a` as a rank-0 array — the cell an operation of rank 0
2454/// sees.
2455fn atom(a: &Array, i: usize) -> Array {
2456    debug_assert!(a.is_row_major(), "an atom out of a column-major buffer");
2457    Array::new(Vec::new(), a.data.slice(i, i + 1))
2458}
2459
2460/// `< y` / `⊂ y`.
2461fn enclose(y: &Array, rule: Enclose) -> Array {
2462    if rule == Enclose::ExceptSimpleScalar && y.rank() == 0 && y.dtype() != DType::Box {
2463        return y.clone();
2464    }
2465    Array::boxed(y.clone())
2466}
2467
2468/// One rank-0 cell opened: a box gives up its contents, anything else is
2469/// its own contents already.
2470///
2471/// What comes out is row-major. A box is filled with a RESULT, and a result
2472/// carries whatever layout its verb left — `|:&.>` boxes column-major
2473/// matrices — while everything downstream of an open reads a value the way
2474/// a verb's argument is read.
2475fn open_cell(y: &Array) -> Array {
2476    match &y.data {
2477        Data::Box(v) if !v.is_empty() => v[0].to_row_major(),
2478        _ => y.clone(),
2479    }
2480}
2481
2482/// `↑ y` (APL): the first element, disclosed. An empty argument has none,
2483/// so its fill stands in.
2484fn first(y: &Array) -> Array {
2485    if y.count() == 0 {
2486        // A nested empty that remembers its items answers with the
2487        // prototype: `↑0⍴⊂2 3⍴9` is the 2 by 3 table of zeros.
2488        if let Some(p) = y.proto() {
2489            return p.clone();
2490        }
2491        let mut d = Data::empty(y.dtype());
2492        d.push_fill();
2493        return open_cell(&Array::new(Vec::new(), d));
2494    }
2495    open_cell(&atom(y, 0))
2496}
2497
2498/// `≡ y` (APL).
2499fn depth(y: &Array) -> i64 {
2500    match &y.data {
2501        Data::Box(v) => 1 + v.iter().map(depth).max().unwrap_or(0),
2502        _ => i64::from(y.rank() > 0),
2503    }
2504}
2505
2506/// Whether every item of `y`, at every level, has its siblings' depth. A
2507/// simple array is uniform, and so is `1 2∘.⍴3 4`, whose items are of one
2508/// depth and different lengths; `1(2(3 4))` and `(1 2),⊂3 4` are not.
2509fn uniform(y: &Array) -> bool {
2510    let Data::Box(v) = &y.data else { return true };
2511    let Some(head) = v.first() else { return true };
2512    let d = depth(head);
2513    v.iter().all(|b| depth(b) == d && uniform(b))
2514}
2515
2516/// Every leaf array inside `a`, in ravel order.
2517///
2518/// A leaf comes out row-major: a box may hold whatever layout the verb that
2519/// filled it left behind, and a caller that reads the ravel would otherwise
2520/// read a column-major buffer as rows.
2521fn leaves(a: &Array, out: &mut Vec<Array>) {
2522    let a = a.to_row_major();
2523    match &a.data {
2524        Data::Box(v) => {
2525            for b in v.iter() {
2526                leaves(b, out);
2527            }
2528        }
2529        _ => out.push(a),
2530    }
2531}
2532
2533/// `∊ y` (APL): every leaf element as one vector. Leaves that share no one
2534/// type make a MIXED SIMPLE vector, as catenating them would.
2535fn enlist(y: &Array, _span: Span) -> Result<Array> {
2536    let mut parts = Vec::new();
2537    leaves(y, &mut parts);
2538    // An empty leaf contributes no elements, so it does not decide the
2539    // type either.
2540    let mut dt = None;
2541    let mut mixing = false;
2542    for p in parts.iter().filter(|p| p.count() > 0) {
2543        dt = Some(match dt {
2544            None => p.dtype(),
2545            Some(t) => match DType::promote(t, p.dtype()) {
2546                Some(t) => t,
2547                None => {
2548                    mixing = true;
2549                    break;
2550                }
2551            },
2552        });
2553    }
2554    if mixing {
2555        let mut cells: Vec<Array> = Vec::new();
2556        for p in &parts {
2557            let p = p.to_row_major();
2558            cells.extend((0..p.count()).map(|i| atom(&p, i)));
2559        }
2560        return Ok(Array::new(vec![cells.len()], Data::Box(cells.into())));
2561    }
2562    let dt = dt.unwrap_or(DType::I64);
2563    let mut data = Data::empty(dt);
2564    for p in &parts {
2565        let cast = p.data.cast(dt).ok_or_else(|| Error::internal("unsupported widening in enlist"))?;
2566        data.extend_from(&cast);
2567    }
2568    Ok(Array::new(vec![data.len()], data))
2569}
2570
2571/// A scalar repeated over `shape` — how a catenation spreads an atom.
2572fn spread(a: &Array, shape: &[usize]) -> Array {
2573    let n: usize = shape.iter().product();
2574    let mut data = Data::empty(a.dtype());
2575    for _ in 0..n {
2576        push_elem(&mut data, &a.data, 0);
2577    }
2578    Array::new(shape.to_vec(), data)
2579}
2580
2581/// Per-axis maximum of two cell shapes, aligned at their trailing axes —
2582/// the same alignment framing uses.
2583fn wider_shape(a: &[usize], b: &[usize]) -> Vec<usize> {
2584    let r = a.len().max(b.len());
2585    let pad = |s: &[usize]| {
2586        let mut v = vec![1usize; r - s.len()];
2587        v.extend_from_slice(s);
2588        v
2589    };
2590    let (pa, pb) = (pad(a), pad(b));
2591    (0..r).map(|k| pa[k].max(pb[k])).collect()
2592}
2593
2594/// `; y` (J): the items of the opened boxes, one after another. A scalar
2595/// among them spreads over the common item shape, as catenation does; the
2596/// rest are padded with fill, which is what makes raze accept items that
2597/// plain catenation would refuse.
2598fn raze(y: &Array, span: Span) -> Result<Array> {
2599    let opened: Vec<Array> = (0..y.count()).map(|i| open_cell(&atom(y, i))).collect();
2600    let mut common: Option<Vec<usize>> = None;
2601    for a in opened.iter().filter(|a| a.rank() > 0) {
2602        common = Some(match common {
2603            None => a.shape[1..].to_vec(),
2604            Some(c) => wider_shape(&c, &a.shape[1..]),
2605        });
2606    }
2607    let common = common.unwrap_or_default();
2608    let mut cells: Vec<Array> = Vec::new();
2609    for a in &opened {
2610        if a.rank() == 0 {
2611            cells.push(spread(a, &common));
2612            continue;
2613        }
2614        for i in 0..a.items() {
2615            cells.push(a.item(i));
2616        }
2617    }
2618    if cells.is_empty() {
2619        return Ok(Array::new(vec![0], Data::empty(DType::I64)));
2620    }
2621    let n = cells.len();
2622    assemble(&[n], cells, span)
2623}
2624
2625/// `x ; y` (J): x boxed, then y — which joins as it is when it is already
2626/// boxed and boxed when it is not.
2627fn link(x: &Array, y: &Array, span: Span) -> Result<Array> {
2628    let head = Array::boxed(x.clone());
2629    let tail = if y.dtype() == DType::Box { y.clone() } else { Array::boxed(y.clone()) };
2630    catenate(&head, &tail, true, false, span)
2631}
2632
2633/// `a` with every element enclosed, where `other` is boxed and `a` is not.
2634/// The shape is kept, so only the depth changes.
2635fn nest_like(a: &Array, other: &Array) -> Array {
2636    if a.dtype() == DType::Box || other.dtype() != DType::Box {
2637        return a.clone();
2638    }
2639    let cells: Vec<Array> = (0..a.count()).map(|i| atom(a, i)).collect();
2640    Array::new(a.shape.clone(), Data::Box(cells.into()))
2641}
2642
2643/// Every ELEMENT of `a` as a rank-0 box, keeping the shape: APL's mixed
2644/// simple form, which is how libjay holds a value whose elements share no
2645/// one type. Enclosing a simple scalar is no change at all in APL, so the
2646/// form says nothing the value did not already say. An already boxed array
2647/// is left alone.
2648fn spread_scalars(a: &Array) -> Array {
2649    if a.dtype() == DType::Box {
2650        return a.clone();
2651    }
2652    let a = a.to_row_major();
2653    let cells: Vec<Array> = (0..a.count()).map(|i| atom(&a, i)).collect();
2654    Array::new(a.shape.clone(), Data::Box(cells.into()))
2655}
2656
2657/// True where every element of `a` is a simple scalar: the shape libjay
2658/// holds a mixed simple array in, whether or not the types still differ.
2659fn holds_scalar_boxes(a: &Array) -> bool {
2660    match a.as_boxes() {
2661        Some(items) => {
2662            !items.is_empty() && items.iter().all(|b| b.rank() == 0 && b.dtype() != DType::Box)
2663        }
2664        None => false,
2665    }
2666}
2667
2668/// The way back out of [`spread_scalars`]: a boxed array whose every
2669/// element is a simple scalar and where one type covers them all is that
2670/// simple array, and in APL always was. Anything else is returned as it is.
2671///
2672/// This runs over every APL result, which is what keeps the form canonical:
2673/// `2↓1 2,'ab'` is the character vector `ab`, not two boxed characters.
2674fn tightened_mixed(a: Array) -> Array {
2675    let common = match a.as_boxes() {
2676        Some(items) if holds_scalar_boxes(&a) => {
2677            let mut t = items[0].dtype();
2678            let mut ok = true;
2679            for b in &items[1..] {
2680                match DType::promote(t, b.dtype()) {
2681                    Some(next) => t = next,
2682                    None => {
2683                        ok = false;
2684                        break;
2685                    }
2686                }
2687            }
2688            ok.then_some(t)
2689        }
2690        _ => None,
2691    };
2692    let Some(common) = common else { return a };
2693    let mut data = Data::empty(common);
2694    for b in a.as_boxes().expect("checked above") {
2695        match b.data.cast(common) {
2696            Some(widened) => push_elem(&mut data, &widened, 0),
2697            None => return a.clone(),
2698        }
2699    }
2700    Array::new(a.shape.clone(), data)
2701}
2702
2703/// A pair put in one form, where one of them is held as boxed scalars and
2704/// the other is not: the simple one is spread into rank-0 boxes so the two
2705/// compare and join element for element. APL only — J's `<2` is a value of
2706/// its own and never the same as `2`.
2707fn align_mixed(x: &Array, y: &Array, apl: bool) -> (Array, Array) {
2708    if apl && holds_scalar_boxes(x) && y.dtype() != DType::Box {
2709        return (x.clone(), spread_scalars(y));
2710    }
2711    if apl && holds_scalar_boxes(y) && x.dtype() != DType::Box {
2712        return (spread_scalars(x), y.clone());
2713    }
2714    (x.clone(), y.clone())
2715}
2716
2717/// Every item of `y` boxed; an already boxed array is left alone.
2718fn box_items(y: &Array) -> Array {
2719    if y.dtype() == DType::Box {
2720        return y.clone();
2721    }
2722    let n = y.items();
2723    let boxes: Vec<Array> = (0..n).map(|i| item_or_self(y, i)).collect();
2724    Array::new(vec![n], Data::Box(boxes.into()))
2725}
2726
2727/// APL vector notation: `x` becomes one more item in front of the strand
2728/// `y`. Simple scalars stay simple, so `1 2 3` is a plain integer vector
2729/// and only a strand holding something else becomes nested.
2730fn strand(x: &Array, y: &Array, span: Span) -> Result<Array> {
2731    let item = enclose(x, Enclose::ExceptSimpleScalar);
2732    let one = |a: &Array| Array::new(vec![1], a.data.clone());
2733    // A strand of one kind stays a plain array; one that mixes characters
2734    // with numbers becomes APL's MIXED SIMPLE array, which libjay keeps as
2735    // boxed scalars. Its depth is 1 and it displays without borders,
2736    // because a box holding a simple scalar is a scalar in APL.
2737    if item.dtype() != DType::Box
2738        && y.dtype() != DType::Box
2739        && DType::promote(item.dtype(), y.dtype()).is_some()
2740    {
2741        return catenate(&one(&item), y, true, false, span);
2742    }
2743    let head = if item.dtype() == DType::Box { item } else { Array::boxed(item) };
2744    catenate(&one(&head), &box_items(y), true, false, span)
2745}
2746
2747// -------------------------------------------------- elementwise operations
2748
2749fn char_arith(span: Span) -> Error {
2750    Error::new(ErrorKind::Type, "cannot do arithmetic on characters", Some(span))
2751}
2752
2753fn symbol_arith(span: Span) -> Error {
2754    Error::new(
2755        ErrorKind::Type,
2756        "cannot do arithmetic on symbols; `5 s:` gives their names back",
2757        Some(span),
2758    )
2759}
2760
2761fn box_arith(span: Span) -> Error {
2762    Error::new(
2763        ErrorKind::Type,
2764        "cannot do arithmetic on boxed values; open them first (J `>`, APL `⊃`)",
2765        Some(span),
2766    )
2767}
2768
2769/// The complaint an operation makes about an element type it cannot work
2770/// on at all.
2771fn wrong_type(d: DType, span: Span) -> Error {
2772    match d {
2773        DType::Box => box_arith(span),
2774        DType::Symbol => symbol_arith(span),
2775        _ => char_arith(span),
2776    }
2777}
2778
2779/// Borrow numeric data as i64, widening a boolean buffer into `tmp`.
2780///
2781/// The widening is a pass over the whole buffer, so it takes the thread
2782/// pool on the sizes that are worth splitting; the values are the same
2783/// whichever way it runs.
2784fn borrow_i64<'a>(d: &'a Data, tmp: &'a mut Vec<i64>) -> &'a [i64] {
2785    match d {
2786        Data::I64(v) => v,
2787        Data::Bool(v) => {
2788            *tmp = par::map(v, |&b| b as i64);
2789            &tmp[..]
2790        }
2791        // Callers exclude character data before reaching here.
2792        _ => &[],
2793    }
2794}
2795
2796/// Borrow numeric data as f64, widening into `tmp` when needed.
2797fn borrow_f64<'a>(d: &'a Data, tmp: &'a mut Vec<f64>) -> &'a [f64] {
2798    match d {
2799        Data::F64(v) => v,
2800        Data::I64(v) => {
2801            *tmp = par::map(v, |&x| x as f64);
2802            &tmp[..]
2803        }
2804        Data::Bool(v) => {
2805            *tmp = par::map(v, |&x| x as f64);
2806            &tmp[..]
2807        }
2808        Data::Ext(v) => {
2809            *tmp = par::map(v, exact::ext_to_f64);
2810            &tmp[..]
2811        }
2812        Data::Rat(v) => {
2813            *tmp = par::map(v, Rat::to_f64);
2814            &tmp[..]
2815        }
2816        _ => &[],
2817    }
2818}
2819
2820/// Borrow numeric data as complex, widening into `tmp` when needed.
2821fn borrow_cx<'a>(d: &'a Data, tmp: &'a mut Vec<Cx>) -> &'a [Cx] {
2822    match d {
2823        Data::Complex(v) => v,
2824        Data::Ext(v) => {
2825            *tmp = par::map(v, |x| [exact::ext_to_f64(x), 0.0]);
2826            &tmp[..]
2827        }
2828        Data::Rat(v) => {
2829            *tmp = par::map(v, |x| [x.to_f64(), 0.0]);
2830            &tmp[..]
2831        }
2832        Data::F64(v) => {
2833            *tmp = par::map(v, |&x| [x, 0.0]);
2834            &tmp[..]
2835        }
2836        Data::I64(v) => {
2837            *tmp = par::map(v, |&x| [x as f64, 0.0]);
2838            &tmp[..]
2839        }
2840        Data::Bool(v) => {
2841            *tmp = v.iter().map(|&x| [x as f64, 0.0]).collect();
2842            &tmp[..]
2843        }
2844        _ => &[],
2845    }
2846}
2847
2848/// One element of a narrow buffer, read as the type a pass computes in.
2849///
2850/// This is what lets a pass over operands of two different types run
2851/// without a widened copy of either: the promotion happens where the
2852/// element is read, inside the chunk, so the only buffer the pass touches
2853/// besides its arguments is its own result. Promotion and then the
2854/// operation is exactly what the widened copy would have fed it, so the
2855/// answers are identical either way.
2856pub(crate) trait Widen<T>: Copy + Send + Sync {
2857    fn widen(self) -> T;
2858}
2859
2860macro_rules! widens {
2861    ($($from:ty => $to:ty : |$v:ident| $e:expr;)*) => {
2862        $(impl Widen<$to> for $from {
2863            #[inline(always)]
2864            fn widen(self) -> $to {
2865                let $v = self;
2866                $e
2867            }
2868        })*
2869    };
2870}
2871
2872widens! {
2873    u8 => i64: |v| v as i64;
2874    i64 => i64: |v| v;
2875    u8 => f64: |v| v as f64;
2876    i64 => f64: |v| v as f64;
2877    f64 => f64: |v| v;
2878    u8 => Cx: |v| [v as f64, 0.0];
2879    i64 => Cx: |v| [v as f64, 0.0];
2880    f64 => Cx: |v| [v, 0.0];
2881    Cx => Cx: |v| v;
2882}
2883
2884/// Bind `$s` to the buffer behind one numeric operand of an integer pass,
2885/// in the buffer's own element type, and evaluate `$body` with it.
2886macro_rules! i64_source {
2887    ($d:expr, $tmp:ident, $s:ident, $body:expr) => {
2888        match $d {
2889            Data::I64(v) => {
2890                let $s: &[i64] = v;
2891                $body
2892            }
2893            Data::Bool(v) => {
2894                let $s: &[u8] = v;
2895                $body
2896            }
2897            other => {
2898                let $s: &[i64] = borrow_i64(other, &mut $tmp);
2899                $body
2900            }
2901        }
2902    };
2903}
2904
2905/// The same for a float pass. The exact types have no fixed-width buffer to
2906/// read element by element, so they keep the widened copy.
2907macro_rules! f64_source {
2908    ($d:expr, $tmp:ident, $s:ident, $body:expr) => {
2909        match $d {
2910            Data::F64(v) => {
2911                let $s: &[f64] = v;
2912                $body
2913            }
2914            Data::I64(v) => {
2915                let $s: &[i64] = v;
2916                $body
2917            }
2918            Data::Bool(v) => {
2919                let $s: &[u8] = v;
2920                $body
2921            }
2922            other => {
2923                let $s: &[f64] = borrow_f64(other, &mut $tmp);
2924                $body
2925            }
2926        }
2927    };
2928}
2929
2930/// The same for a complex pass.
2931macro_rules! cx_source {
2932    ($d:expr, $tmp:ident, $s:ident, $body:expr) => {
2933        match $d {
2934            Data::Complex(v) => {
2935                let $s: &[Cx] = v;
2936                $body
2937            }
2938            Data::F64(v) => {
2939                let $s: &[f64] = v;
2940                $body
2941            }
2942            Data::I64(v) => {
2943                let $s: &[i64] = v;
2944                $body
2945            }
2946            Data::Bool(v) => {
2947                let $s: &[u8] = v;
2948                $body
2949            }
2950            other => {
2951                let $s: &[Cx] = borrow_cx(other, &mut $tmp);
2952                $body
2953            }
2954        }
2955    };
2956}
2957
2958/// Numeric data as f64, borrowed when it already is that.
2959fn as_f64<'a>(d: &'a Data, tmp: &'a mut Vec<f64>, span: Span) -> Result<&'a [f64]> {
2960    if !d.dtype().is_numeric() {
2961        return Err(wrong_type(d.dtype(), span));
2962    }
2963    Ok(borrow_f64(d, tmp))
2964}
2965
2966/// The type an arithmetic pair computes in. Booleans count as integers.
2967fn arith_type(a: DType, b: DType, span: Span) -> Result<DType> {
2968    if a == DType::Box || b == DType::Box {
2969        return Err(box_arith(span));
2970    }
2971    if a == DType::Symbol || b == DType::Symbol {
2972        return Err(symbol_arith(span));
2973    }
2974    match DType::promote(a, b) {
2975        Some(DType::Char) => Err(char_arith(span)),
2976        None => Err(Error::new(
2977            ErrorKind::Type,
2978            "cannot mix character and numeric data",
2979            Some(span),
2980        )),
2981        Some(DType::Bool) => Ok(DType::I64),
2982        Some(t) => Ok(t),
2983    }
2984}
2985
2986/// Apply `f` to the argument pair behind every element of one output chunk.
2987/// Element `start + k` of the result pairs `xs[xoff + (start+k)/xdiv]` with
2988/// `ys[yoff + (start+k)/ydiv]`, so broadcasting and folding both run without
2989/// materialising cells.
2990///
2991/// The two shapes that carry the work — one element per element, and one
2992/// element spread over a whole chunk — become plain loops over slices, which
2993/// is what lets the compiler vectorise the pass; anything else keeps the
2994/// general index arithmetic. `f` returns false to abandon the chunk.
2995///
2996/// The two sides carry their own element types, so a pass over operands of
2997/// different widths reads each buffer as it lies and promotes inside `f`.
2998#[allow(clippy::too_many_arguments)]
2999#[inline]
3000fn zip_chunk<A, B, U, F>(
3001    xs: &[A],
3002    xoff: usize,
3003    xdiv: usize,
3004    ys: &[B],
3005    yoff: usize,
3006    ydiv: usize,
3007    start: usize,
3008    out: &mut [U],
3009    mut f: F,
3010) -> bool
3011where
3012    A: Copy,
3013    B: Copy,
3014    F: FnMut(A, B, &mut U) -> bool,
3015{
3016    let len = out.len();
3017    if len == 0 {
3018        return true;
3019    }
3020    let last = start + len - 1;
3021    let one_x = xdiv > 1 && start / xdiv == last / xdiv;
3022    let one_y = ydiv > 1 && start / ydiv == last / ydiv;
3023    if xdiv == 1 && ydiv == 1 {
3024        let xc = &xs[xoff + start..xoff + start + len];
3025        let yc = &ys[yoff + start..yoff + start + len];
3026        for ((slot, &a), &b) in out.iter_mut().zip(xc).zip(yc) {
3027            if !f(a, b, slot) {
3028                return false;
3029            }
3030        }
3031    } else if xdiv == 1 && one_y {
3032        let b = ys[yoff + start / ydiv];
3033        let xc = &xs[xoff + start..xoff + start + len];
3034        for (slot, &a) in out.iter_mut().zip(xc) {
3035            if !f(a, b, slot) {
3036                return false;
3037            }
3038        }
3039    } else if one_x && ydiv == 1 {
3040        let a = xs[xoff + start / xdiv];
3041        let yc = &ys[yoff + start..yoff + start + len];
3042        for (slot, &b) in out.iter_mut().zip(yc) {
3043            if !f(a, b, slot) {
3044                return false;
3045            }
3046        }
3047    } else {
3048        for (k, slot) in out.iter_mut().enumerate() {
3049            let i = start + k;
3050            if !f(xs[xoff + i / xdiv], ys[yoff + i / ydiv], slot) {
3051                return false;
3052            }
3053        }
3054    }
3055    true
3056}
3057
3058// ------------------------------------------------- factorial and binomial
3059
3060/// Lanczos coefficients for g = 7, the published nine-term series.
3061const LANCZOS: [f64; 9] = [
3062    0.999_999_999_999_809_9,
3063    676.520_368_121_885_1,
3064    -1_259.139_216_722_402_8,
3065    771.323_428_777_653_1,
3066    -176.615_029_162_140_6,
3067    12.507_343_278_686_905,
3068    -0.138_571_095_265_720_12,
3069    9.984_369_578_019_572e-6,
3070    1.505_632_735_149_311_6e-7,
3071];
3072
3073/// The gamma function on the reals, by the Lanczos approximation (relative
3074/// error below 1e-13 over the range that stays finite). Poles are left to
3075/// the callers, which know the sign the limit approaches from.
3076fn gamma(x: f64) -> f64 {
3077    use std::f64::consts::PI;
3078    if x < 0.5 {
3079        // Reflection carries the negative half onto the positive one.
3080        return PI / ((PI * x).sin() * gamma(1.0 - x));
3081    }
3082    let z = x - 1.0;
3083    let mut a = LANCZOS[0];
3084    for (i, &c) in LANCZOS.iter().enumerate().skip(1) {
3085        a += c / (z + i as f64);
3086    }
3087    let t = z + 7.5;
3088    (2.0 * PI).sqrt() * t.powf(z + 0.5) * (-t).exp() * a
3089}
3090
3091/// `! y`: gamma(y+1). Integers up to 20! are exact in f64 and every
3092/// factorial is one in J, which is why this never returns an integer.
3093fn factorial(y: f64) -> f64 {
3094    if y.fract() == 0.0 && y.abs() < 1e17 {
3095        let n = y as i64;
3096        if n < 0 {
3097            // A pole: the limit alternates sign as the argument walks left.
3098            return if n % 2 == -1 { f64::INFINITY } else { f64::NEG_INFINITY };
3099        }
3100        if n > 170 {
3101            return f64::INFINITY;
3102        }
3103        let mut c = 1.0f64;
3104        for i in 2..=n {
3105            c *= i as f64;
3106        }
3107        return c;
3108    }
3109    gamma(y + 1.0)
3110}
3111
3112/// `! y` under the dialect's rule for an argument the gamma function cannot
3113/// reach at all. jconsole answers `_` wherever its own gamma overflows —
3114/// `! _`, `! 1e308` and `! _1e20` are each `_` — and refuses `! __` alone,
3115/// which is the NaN this leaves standing. The APL caller refuses every
3116/// non-finite answer and so needs no rule of its own.
3117fn factorial_as(y: f64, tol: Tol) -> f64 {
3118    let r = factorial(y);
3119    if tol.is_j() && r.is_nan() && !y.is_nan() && y != f64::NEG_INFINITY {
3120        return f64::INFINITY;
3121    }
3122    r
3123}
3124
3125/// The largest left argument the product form of the binomial is taken for;
3126/// beyond it the gamma quotient is both faster and accurate enough.
3127const BINOMIAL_PRODUCT_LIMIT: i64 = 4096;
3128
3129/// `x ! y` for a nonnegative whole x: the falling factorial over `x!`, one
3130/// factor at a time so that no partial product overflows more than the
3131/// result does.
3132fn binomial_product(x: i64, y: f64) -> f64 {
3133    let mut c = 1.0f64;
3134    for i in 1..=x {
3135        c = c * (y - i as f64 + 1.0) / i as f64;
3136        if c == 0.0 {
3137            break;
3138        }
3139    }
3140    c
3141}
3142
3143/// The two whole-number cases J answers with an exact integer: a
3144/// nonnegative x, and a negative x against a y at least as negative (the
3145/// upper-negation identity). None when the value leaves i64.
3146fn binomial_i64(x: i64, y: i64) -> Option<i64> {
3147    if x < 0 {
3148        // C(y, x) is zero for a negative x unless y is negative too and no
3149        // greater, where C(y,x) = (-1)^(y-x) C(-x-1, -y-1).
3150        if y >= 0 || y < x {
3151            return Some(0);
3152        }
3153        let v = binomial_exact(-y - 1, -x - 1)?;
3154        return if (y - x) % 2 == 0 { Some(v) } else { v.checked_neg() };
3155    }
3156    binomial_exact(x, y)
3157}
3158
3159/// `x ! y` in exact integers for a nonnegative whole x. Every partial value
3160/// is itself a binomial coefficient, so the division is always exact.
3161fn binomial_exact(x: i64, y: i64) -> Option<i64> {
3162    if x > BINOMIAL_PRODUCT_LIMIT {
3163        return None;
3164    }
3165    let mut c: i128 = 1;
3166    for i in 1..=x as i128 {
3167        c = c.checked_mul(y as i128 - i + 1)? / i;
3168        if c == 0 {
3169            break;
3170        }
3171    }
3172    i64::try_from(c).ok()
3173}
3174
3175/// `x ! y` where an operand is infinite, which the gamma quotient reaches
3176/// only as a NaN. jconsole answers most of these and refuses the rest, and
3177/// the table below is what nineteen probes of it say, entry by entry: an
3178/// infinite LEFT argument gives 0 unless the right one sits on a pole of
3179/// the gamma function; an infinite RIGHT one is read off the left's sign;
3180/// and of the four infinite pairs only `__ ! _` has a value. None is a NaN
3181/// the caller then refuses.
3182fn binomial_at_infinity(x: f64, y: f64) -> Option<f64> {
3183    if x.is_infinite() && y.is_infinite() {
3184        return (x < 0.0 && y > 0.0).then_some(0.0);
3185    }
3186    if x.is_infinite() {
3187        // `_ ! _1` and `_ ! _2` have none; `_ ! _2.5` is 0, because only a
3188        // whole negative right argument is a pole.
3189        return (!(y < 0.0 && y.fract() == 0.0)).then_some(0.0);
3190    }
3191    if x > 0.0 {
3192        Some(f64::INFINITY)
3193    } else if x == 0.0 {
3194        Some(1.0)
3195    } else {
3196        Some(0.0)
3197    }
3198}
3199
3200/// `x ! y` on the reals.
3201fn binomial(x: f64, y: f64) -> f64 {
3202    if x.is_nan() || y.is_nan() {
3203        // A NaN the program wrote travels: `_ ! _.` is `_.`, not a value
3204        // read off the table below.
3205        return f64::NAN;
3206    }
3207    if x.is_infinite() || y.is_infinite() {
3208        return binomial_at_infinity(x, y).unwrap_or(f64::NAN);
3209    }
3210    if x.fract() == 0.0 && x.abs() < 1e17 {
3211        let xi = x as i64;
3212        if xi < 0 {
3213            if y.fract() == 0.0 && y < 0.0 && y >= x {
3214                let sign = if (y as i64 - xi) % 2 == 0 { 1.0 } else { -1.0 };
3215                return sign * binomial_product(-y as i64 - 1, -x - 1.0);
3216            }
3217            return 0.0;
3218        }
3219        if xi <= BINOMIAL_PRODUCT_LIMIT {
3220            return binomial_product(xi, y);
3221        }
3222    }
3223    gamma(y + 1.0) / (gamma(x + 1.0) * gamma(y - x + 1.0))
3224}
3225
3226/// One integer step. None means the result left i64 — an overflow, or a
3227/// value that is not an integer — and the whole pass is redone in f64.
3228#[inline]
3229fn i64_op(op: ScalarDyad, a: i64, b: i64) -> Option<i64> {
3230    use ScalarDyad::*;
3231    Some(match op {
3232        Add => a.checked_add(b)?,
3233        Sub => a.checked_sub(b)?,
3234        Mul => a.checked_mul(b)?,
3235        Min => a.min(b),
3236        Max => a.max(b),
3237        Residue => {
3238            if a == 0 {
3239                b
3240            } else {
3241                // wrapping_rem: i64::MIN % -1 is mathematically 0.
3242                let mut r = b.wrapping_rem(a);
3243                if r != 0 && (r < 0) != (a < 0) {
3244                    r += a;
3245                }
3246                r
3247            }
3248        }
3249        Pow => {
3250            if b < 0 {
3251                return None;
3252            }
3253            a.checked_pow(u32::try_from(b).ok()?)?
3254        }
3255        Binomial => binomial_i64(a, b)?,
3256        _ => return None,
3257    })
3258}
3259
3260/// One float step.
3261#[inline]
3262fn f64_op(op: ScalarDyad, a: f64, b: f64, tol: Tol, span: Span) -> Result<f64> {
3263    use ScalarDyad::*;
3264    let r = match op {
3265        Add => a + b,
3266        Sub => a - b,
3267        Mul => tol.mul(a, b),
3268        Min => a.min(b),
3269        Max => a.max(b),
3270        DivJ => {
3271            if b == 0.0 {
3272                if a == 0.0 { 0.0 } else { f64::INFINITY.copysign(a) }
3273            } else {
3274                a / b
3275            }
3276        }
3277        DivApl => {
3278            if b == 0.0 {
3279                if a == 0.0 {
3280                    1.0
3281                } else {
3282                    return Err(Error::domain("division by zero", span));
3283                }
3284            } else {
3285                a / b
3286            }
3287        }
3288        Pow => {
3289            if a == 0.0 && b == 0.0 {
3290                1.0
3291            } else if a == 0.0 && b < 0.0 && !tol.is_j() {
3292                // GNU APL refuses `0⋆¯1`: it is a division by zero under
3293                // another name, and its `÷0` is refused too. J answers the
3294                // infinity, as its `% 0` does.
3295                return Err(Error::domain("zero has no negative power", span));
3296            } else if a < 0.0 && b.is_infinite() {
3297                // A negative base under an infinite exponent alternates in
3298                // sign for ever. jconsole answers only where the magnitude
3299                // falls to zero and the sign stops mattering — `_2 ^ __` is
3300                // 0 — and refuses the rest, `_1 ^ _` and `_2 ^ _` alike.
3301                if a.abs() != 1.0 && (a.abs() > 1.0) == (b < 0.0) {
3302                    0.0
3303                } else {
3304                    return Err(Error::domain(
3305                        "a negative base has no infinite power: the sign alternates",
3306                        span,
3307                    ));
3308                }
3309            } else {
3310                a.powf(b)
3311            }
3312        }
3313        Residue => tol.residue(a, b),
3314        Log => {
3315            if a < 0.0 || b < 0.0 {
3316                return Err(Error::not_yet("complex numbers", span));
3317            }
3318            let r = b.ln() / a.ln();
3319            // GNU APL has no infinite logarithm: `1⍟2`, `2⍟0` and `1⍟0` are
3320            // all DOMAIN ERROR. The two it does define where the ratio is a
3321            // NaN — `0⍟0` and `1⍟1` — are 1, each of them a base raised to
3322            // the first power. J keeps the infinity (`1 ^. 2` is `_`) and
3323            // refuses only the NaN, which the check below the match does.
3324            if !tol.is_j() && !r.is_finite() {
3325                if r.is_nan() {
3326                    return Ok(1.0);
3327                }
3328                return Err(Error::domain("this logarithm has no value", span));
3329            }
3330            r
3331        }
3332        Root => {
3333            if b < 0.0 {
3334                return Err(Error::not_yet("complex numbers", span));
3335            }
3336            b.powf(1.0 / a)
3337        }
3338        // `?`, not `return`: `1 o. _` is a NaN the arithmetic made, and
3339        // jconsole refuses it (as a limit error) rather than answering.
3340        Circle => {
3341            let r = circle(a, b, span)?;
3342            // GNU APL refuses a circle function with no value where J
3343            // continues it: `¯7○1` is artanh at its pole, an infinity in J
3344            // and a DOMAIN ERROR there.
3345            if !tol.is_j() && !r.is_finite() && a.is_finite() && b.is_finite() {
3346                return Err(Error::domain("this circle function has no value", span));
3347            }
3348            r
3349        }
3350        Binomial => binomial(a, b),
3351        _ => return Err(Error::internal("non-arithmetic op in the float path")),
3352    };
3353    if tol.made_nan(r, a, b) {
3354        return Err(nan_error(op, a, b, span));
3355    }
3356    Ok(r)
3357}
3358
3359/// The diagnostic for arithmetic with no value, naming the pair that has
3360/// none: "NaN error: `_ - _` has no value".
3361#[cold]
3362fn nan_error(op: ScalarDyad, a: f64, b: f64, span: Span) -> Error {
3363    Error::nan(
3364        format!(
3365            "`{} {} {}` has no value",
3366            j_number(a),
3367            crate::fuse::dyad_name(op),
3368            j_number(b)
3369        ),
3370        span,
3371    )
3372}
3373
3374/// Which of a real pair's operations has no real answer, so the whole pass
3375/// runs in the complex domain instead. Only the four operations that can
3376/// leave the reals are asked.
3377#[inline]
3378fn escapes_reals(op: ScalarDyad, a: f64, b: f64) -> bool {
3379    use ScalarDyad::*;
3380    match op {
3381        // An integer exponent keeps a negative base real (`_1 ^ 2` is 1).
3382        // An INFINITE one is neither integer nor fractional: `fract` is a
3383        // NaN there, and the pair belongs to the real path, which answers
3384        // `_2 ^ __` with 0 and refuses the rest.
3385        Pow => a < 0.0 && b.is_finite() && b.fract() != 0.0,
3386        Log => a < 0.0 || b < 0.0,
3387        Root => b < 0.0,
3388        Circle => circle_escapes(a, b),
3389        _ => false,
3390    }
3391}
3392
3393/// The circle functions with no real answer at a real argument. A
3394/// non-integer k is a domain error, which the real path reports.
3395#[inline]
3396fn circle_escapes(k: f64, y: f64) -> bool {
3397    if k.fract() != 0.0 {
3398        return false;
3399    }
3400    match k as i64 {
3401        0 | -1 | -2 | -7 => y.abs() > 1.0,
3402        -4 => y.abs() < 1.0,
3403        -6 => y < 1.0,
3404        // The functions built on the imaginary unit, which no real argument
3405        // escapes.
3406        8 | -8 | -11 | -12 => true,
3407        _ => false,
3408    }
3409}
3410
3411/// `k o. y`: the circle function k applied to a real y.
3412///
3413/// The table is J's and APL's alike (they share it): 1 2 3 are sine, cosine
3414/// and tangent, 5 6 7 their hyperbolic counterparts, a negative k inverts the
3415/// function at |k|, and 0 and 4 are the two Pythagorean forms. 9 to 12 read
3416/// the parts of a complex number — real, magnitude, imaginary, phase — and
3417/// are answered here for the reals they also accept. A pair whose answer
3418/// leaves the reals never reaches this function: [`escapes_reals`] sends the
3419/// whole pass to the complex path first.
3420#[inline]
3421fn circle(k: f64, y: f64, span: Span) -> Result<f64> {
3422    if k.fract() != 0.0 {
3423        return Err(Error::domain("the circle function needs an integer left argument", span));
3424    }
3425    let complex = || Error::internal("a circle function left the reals on the real path");
3426    Ok(match k as i64 {
3427        0 => {
3428            if y.abs() > 1.0 {
3429                return Err(complex());
3430            }
3431            (1.0 - y * y).max(0.0).sqrt()
3432        }
3433        1 => y.sin(),
3434        2 => y.cos(),
3435        3 => y.tan(),
3436        4 => (1.0 + y * y).sqrt(),
3437        5 => y.sinh(),
3438        6 => y.cosh(),
3439        7 => y.tanh(),
3440        -1 => {
3441            if y.abs() > 1.0 {
3442                return Err(complex());
3443            }
3444            y.asin()
3445        }
3446        -2 => {
3447            if y.abs() > 1.0 {
3448                return Err(complex());
3449            }
3450            y.acos()
3451        }
3452        -3 => y.atan(),
3453        -4 => {
3454            if y.abs() < 1.0 {
3455                return Err(complex());
3456            }
3457            // The sign follows y: `_4 o. _2` is `_1.73205`, not `1.73205`.
3458            y.signum() * (y * y - 1.0).max(0.0).sqrt()
3459        }
3460        -5 => y.asinh(),
3461        -6 => {
3462            if y < 1.0 {
3463                return Err(complex());
3464            }
3465            y.acosh()
3466        }
3467        -7 => {
3468            if y.abs() > 1.0 {
3469                return Err(complex());
3470            }
3471            y.atanh()
3472        }
3473        // The parts of a number that happens to be real.
3474        9 | -9 | -10 => y,
3475        10 => y.abs(),
3476        11 => 0.0,
3477        12 => {
3478            if y < 0.0 {
3479                std::f64::consts::PI
3480            } else {
3481                0.0
3482            }
3483        }
3484        8 | -8 | -11 | -12 => return Err(complex()),
3485        _ => {
3486            return Err(Error::domain(
3487                "the circle functions run from _12 to 12",
3488                span,
3489            ));
3490        }
3491    })
3492}
3493
3494/// One complex step.
3495#[inline]
3496fn cx_op(op: ScalarDyad, a: Cx, b: Cx, span: Span) -> Result<Cx> {
3497    use ScalarDyad::*;
3498    Ok(match op {
3499        Add => cx::add(a, b),
3500        Sub => cx::sub(a, b),
3501        Mul => cx::mul(a, b),
3502        DivJ => cx::div(a, b),
3503        DivApl => {
3504            if b == cx::ZERO {
3505                if a == cx::ZERO {
3506                    cx::ONE
3507                } else {
3508                    return Err(Error::domain("division by zero", span));
3509                }
3510            } else {
3511                cx::div(a, b)
3512            }
3513        }
3514        Pow => cx::pow(a, b),
3515        Log => cx::log(a, b),
3516        Root => cx::root(a, b),
3517        Residue => cx::residue(a, b),
3518        Lcm => cx::lcm(a, b),
3519        Gcd => cx::gcd(a, b),
3520        MakeComplex => cx::add(a, cx::mul(cx::I, b)),
3521        PolarBy => cx::mul(a, cx::exp(cx::mul(cx::I, b))),
3522        Circle => {
3523            if a[1] != 0.0 || a[0].fract() != 0.0 {
3524                return Err(Error::domain(
3525                    "the circle function needs an integer left argument",
3526                    span,
3527                ));
3528            }
3529            cx::circle(a[0] as i64, b).ok_or_else(|| {
3530                Error::domain("the circle functions run from _12 to 12", span)
3531            })?
3532        }
3533        Min | Max => return Err(no_complex_order(span)),
3534        Binomial => {
3535            return Err(Error::not_yet("the binomial function on complex numbers", span));
3536        }
3537        Eq | Ne | Lt | Le | Gt | Ge => {
3538            return Err(Error::internal("a comparison in the complex arithmetic path"));
3539        }
3540    })
3541}
3542
3543/// The complaint an ordering makes about complex operands. Both references
3544/// refuse it: complex numbers carry no order, only equality.
3545fn no_complex_order(span: Span) -> Error {
3546    Error::new(
3547        ErrorKind::Domain,
3548        "complex numbers have no order; only equality (=, ~:) applies to them",
3549        Some(span),
3550    )
3551}
3552
3553#[allow(clippy::too_many_arguments)]
3554#[inline(always)]
3555fn dyad_cx_chunk_body<A: Widen<Cx>, B: Widen<Cx>>(
3556    op: ScalarDyad,
3557    xs: &[A],
3558    xoff: usize,
3559    xdiv: usize,
3560    ys: &[B],
3561    yoff: usize,
3562    ydiv: usize,
3563    start: usize,
3564    out: &mut [Cx],
3565    span: Span,
3566) -> Result<()> {
3567    use ScalarDyad::*;
3568    // The three steps that cannot fail are picked before the loop, so the
3569    // pass is one operation per element rather than a match per element.
3570    macro_rules! plain {
3571        ($step:expr) => {{
3572            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut Cx| {
3573                *slot = $step(a.widen(), b.widen());
3574                true
3575            });
3576            return Ok(());
3577        }};
3578    }
3579    match op {
3580        Add => plain!(cx::add),
3581        Sub => plain!(cx::sub),
3582        Mul => plain!(cx::mul),
3583        DivJ => plain!(cx::div),
3584        _ => {}
3585    }
3586    let mut err = None;
3587    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut Cx| {
3588        match cx_op(op, a.widen(), b.widen(), span) {
3589            Ok(v) => {
3590                *slot = v;
3591                true
3592            }
3593            Err(e) => {
3594                err = Some(e);
3595                false
3596            }
3597        }
3598    });
3599    match err {
3600        Some(e) => Err(e),
3601        None => Ok(()),
3602    }
3603}
3604
3605multiversioned! {
3606    /// One chunk of a complex pass, compiled per CPU feature level. Either
3607    /// operand may be narrower than complex, and is promoted as it is read.
3608    #[allow(clippy::too_many_arguments)]
3609    fn dyad_cx_chunk[A: Widen<Cx>, B: Widen<Cx>](
3610        op: ScalarDyad,
3611        xs: &[A],
3612        xoff: usize,
3613        xdiv: usize,
3614        ys: &[B],
3615        yoff: usize,
3616        ydiv: usize,
3617        start: usize,
3618        out: &mut [Cx],
3619        span: Span,
3620    ) -> Result<()> = dyad_cx_chunk_body;
3621}
3622
3623#[allow(clippy::too_many_arguments)]
3624fn dyad_cx<A: Widen<Cx>, B: Widen<Cx>>(
3625    op: ScalarDyad,
3626    xs: &[A],
3627    xoff: usize,
3628    xdiv: usize,
3629    ys: &[B],
3630    yoff: usize,
3631    ydiv: usize,
3632    n: usize,
3633    span: Span,
3634) -> Result<Vec<Cx>> {
3635    par::try_fill(n, |start, part| {
3636        dyad_cx_chunk(op, xs, xoff, xdiv, ys, yoff, ydiv, start, part, span)
3637    })
3638}
3639
3640/// One complex pass over two buffers.
3641///
3642/// An operand that is not complex already is read in its own type and
3643/// promoted element by element, so the pass allocates nothing but its
3644/// result. Only the exact types, which have no fixed-width buffer, are
3645/// widened into one first — and a pass with no complex operand at all (`j.`
3646/// of two reals, a power that leaves the reals) with them, since promoting
3647/// two whole buffers is what such a pass is for.
3648#[allow(clippy::too_many_arguments)]
3649fn complex_dyad_data(
3650    op: ScalarDyad,
3651    x: &Data,
3652    xoff: usize,
3653    xdiv: usize,
3654    y: &Data,
3655    yoff: usize,
3656    ydiv: usize,
3657    n: usize,
3658    span: Span,
3659) -> Result<Data> {
3660    let (mut tx, mut ty) = (Vec::new(), Vec::new());
3661    macro_rules! pass {
3662        ($xs:expr, $ys:expr) => {
3663            Data::Complex(dyad_cx(op, $xs, xoff, xdiv, $ys, yoff, ydiv, n, span)?.into())
3664        };
3665    }
3666    Ok(match (x, y) {
3667        (Data::Complex(a), _) => {
3668            let xs: &[Cx] = a;
3669            cx_source!(y, ty, ys, pass!(xs, ys))
3670        }
3671        (_, Data::Complex(b)) => {
3672            let ys: &[Cx] = b;
3673            cx_source!(x, tx, xs, pass!(xs, ys))
3674        }
3675        _ => pass!(borrow_cx(x, &mut tx), borrow_cx(y, &mut ty)),
3676    })
3677}
3678
3679/// `9 o.` to `12 o.` read a part of a number — real, magnitude, imaginary,
3680/// phase — so their answers are real however complex the argument was. J
3681/// reports them as floats rather than as complex values with a zero
3682/// imaginary part.
3683fn circle_reads_a_part(x: &Data, xoff: usize, xdiv: usize, n: usize) -> bool {
3684    if x.dtype() == DType::Complex {
3685        // A complex left argument selects nothing; the pass reports it.
3686        return false;
3687    }
3688    let mut tmp = Vec::new();
3689    let xs = borrow_f64(x, &mut tmp);
3690    (0..n).all(|i| {
3691        let k = xs[xoff + i / xdiv];
3692        k.fract() == 0.0 && (9.0..=12.0).contains(&k)
3693    })
3694}
3695
3696/// Does the real pass hold an argument pair whose answer leaves the reals?
3697/// One extra scan, and only for the four operations that can.
3698#[allow(clippy::too_many_arguments)]
3699fn pass_leaves_reals(
3700    op: ScalarDyad,
3701    x: &Data,
3702    xoff: usize,
3703    xdiv: usize,
3704    y: &Data,
3705    yoff: usize,
3706    ydiv: usize,
3707    n: usize,
3708) -> bool {
3709    use ScalarDyad::*;
3710    if !matches!(op, Pow | Log | Root | Circle) {
3711        return false;
3712    }
3713    let (mut tx, mut ty) = (Vec::new(), Vec::new());
3714    let xs = borrow_f64(x, &mut tx);
3715    let ys = borrow_f64(y, &mut ty);
3716    (0..n).any(|i| escapes_reals(op, xs[xoff + i / xdiv], ys[yoff + i / ydiv]))
3717}
3718
3719#[allow(clippy::too_many_arguments)]
3720#[inline(always)]
3721fn dyad_i64_chunk_body<A: Widen<i64>, B: Widen<i64>>(
3722    op: ScalarDyad,
3723    xs: &[A],
3724    xoff: usize,
3725    xdiv: usize,
3726    ys: &[B],
3727    yoff: usize,
3728    ydiv: usize,
3729    start: usize,
3730    out: &mut [i64],
3731) -> bool {
3732    use ScalarDyad::*;
3733    // The overflow of the three growing operations is folded into a flag
3734    // rather than breaking the loop: that keeps the pass branch-free, and an
3735    // overflowing chunk is thrown away and redone in f64 in any case.
3736    macro_rules! overflowing {
3737        ($m:ident) => {{
3738            let mut over = false;
3739            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut i64| {
3740                let (v, o) = i64::$m(a.widen(), b.widen());
3741                *slot = v;
3742                over |= o;
3743                true
3744            });
3745            !over
3746        }};
3747    }
3748    macro_rules! plain {
3749        ($step:expr) => {{
3750            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut i64| {
3751                *slot = $step(a.widen(), b.widen());
3752                true
3753            })
3754        }};
3755    }
3756    match op {
3757        Add => overflowing!(overflowing_add),
3758        Sub => overflowing!(overflowing_sub),
3759        Mul => overflowing!(overflowing_mul),
3760        Min => plain!(i64::min),
3761        Max => plain!(i64::max),
3762        _ => zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut i64| {
3763            match i64_op(op, a.widen(), b.widen()) {
3764                Some(v) => {
3765                    *slot = v;
3766                    true
3767                }
3768                None => false,
3769            }
3770        }),
3771    }
3772}
3773
3774multiversioned! {
3775    /// One chunk of an integer pass. False means the chunk left i64 and the
3776    /// caller redoes the whole operation in f64.
3777    ///
3778    /// This is one of the loops compiled per CPU feature level: a chunk is
3779    /// thousands of elements, so choosing the compilation costs nothing
3780    /// against the pass it chooses.
3781    #[allow(clippy::too_many_arguments)]
3782    fn dyad_i64_chunk[A: Widen<i64>, B: Widen<i64>](
3783        op: ScalarDyad,
3784        xs: &[A],
3785        xoff: usize,
3786        xdiv: usize,
3787        ys: &[B],
3788        yoff: usize,
3789        ydiv: usize,
3790        start: usize,
3791        out: &mut [i64],
3792    ) -> bool = dyad_i64_chunk_body;
3793}
3794
3795/// One elementwise integer pass. None means it left i64 anywhere.
3796#[allow(clippy::too_many_arguments)]
3797fn dyad_i64<A: Widen<i64>, B: Widen<i64>>(
3798    op: ScalarDyad,
3799    xs: &[A],
3800    xoff: usize,
3801    xdiv: usize,
3802    ys: &[B],
3803    yoff: usize,
3804    ydiv: usize,
3805    n: usize,
3806) -> Option<Vec<i64>> {
3807    let (out, ok) = par::fill(n, |start, part| {
3808        dyad_i64_chunk(op, xs, xoff, xdiv, ys, yoff, ydiv, start, part)
3809    });
3810    ok.then_some(out)
3811}
3812
3813/// One elementwise integer pass over two buffers, each read in its own
3814/// element type. None means it left i64 anywhere.
3815#[allow(clippy::too_many_arguments)]
3816fn int_dyad_data(
3817    op: ScalarDyad,
3818    x: &Data,
3819    xoff: usize,
3820    xdiv: usize,
3821    y: &Data,
3822    yoff: usize,
3823    ydiv: usize,
3824    n: usize,
3825) -> Option<Data> {
3826    let (mut tx, mut ty) = (Vec::new(), Vec::new());
3827    let out = i64_source!(x, tx, xs, {
3828        i64_source!(y, ty, ys, dyad_i64(op, xs, xoff, xdiv, ys, yoff, ydiv, n))
3829    })?;
3830    Some(Data::I64(out.into()))
3831}
3832
3833#[allow(clippy::too_many_arguments)]
3834#[inline(always)]
3835fn dyad_f64_chunk_body<A: Widen<f64>, B: Widen<f64>>(
3836    op: ScalarDyad,
3837    xs: &[A],
3838    xoff: usize,
3839    xdiv: usize,
3840    ys: &[B],
3841    yoff: usize,
3842    ydiv: usize,
3843    start: usize,
3844    out: &mut [f64],
3845    tol: Tol,
3846    span: Span,
3847) -> Result<()> {
3848    use ScalarDyad::*;
3849    // The arithmetic that cannot fail is picked before the loop, so the
3850    // compiler sees one operation per pass instead of a match per element.
3851    macro_rules! plain {
3852        ($step:expr) => {{
3853            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut f64| {
3854                *slot = $step(a.widen(), b.widen());
3855                true
3856            });
3857            return Ok(());
3858        }};
3859    }
3860    // The arithmetic that cannot fail runs in the plain loop; under J's
3861    // rules a NaN in what it wrote means the pass has to be redone one pair
3862    // at a time, because only there are both operands in hand to tell a NaN
3863    // the arithmetic MADE from one the program wrote. The scan itself
3864    // vectorises and finds nothing on ordinary data, so the fast path keeps
3865    // its speed and the slow one keeps the rule.
3866    macro_rules! plain_checked {
3867        ($step:expr) => {{
3868            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut f64| {
3869                *slot = $step(a.widen(), b.widen());
3870                true
3871            });
3872            if !(tol.is_j() && out.iter().any(|v| v.is_nan())) {
3873                return Ok(());
3874            }
3875        }};
3876    }
3877    match op {
3878        Add => plain_checked!(|a: f64, b: f64| a + b),
3879        Sub => plain_checked!(|a: f64, b: f64| a - b),
3880        Mul => plain_checked!(|a: f64, b: f64| a * b),
3881        Min => plain!(f64::min),
3882        Max => plain!(f64::max),
3883        _ => {}
3884    }
3885    let mut err = None;
3886    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut f64| {
3887        match f64_op(op, a.widen(), b.widen(), tol, span) {
3888            Ok(v) => {
3889                *slot = v;
3890                true
3891            }
3892            Err(e) => {
3893                err = Some(e);
3894                false
3895            }
3896        }
3897    });
3898    match err {
3899        Some(e) => Err(e),
3900        None => Ok(()),
3901    }
3902}
3903
3904multiversioned! {
3905    /// One chunk of a float pass, compiled per CPU feature level. Either
3906    /// operand may be an integer or a boolean buffer, promoted as it is read.
3907    #[allow(clippy::too_many_arguments)]
3908    fn dyad_f64_chunk[A: Widen<f64>, B: Widen<f64>](
3909        op: ScalarDyad,
3910        xs: &[A],
3911        xoff: usize,
3912        xdiv: usize,
3913        ys: &[B],
3914        yoff: usize,
3915        ydiv: usize,
3916        start: usize,
3917        out: &mut [f64],
3918        tol: Tol,
3919        span: Span,
3920    ) -> Result<()> = dyad_f64_chunk_body;
3921}
3922
3923#[allow(clippy::too_many_arguments)]
3924fn dyad_f64<A: Widen<f64>, B: Widen<f64>>(
3925    op: ScalarDyad,
3926    xs: &[A],
3927    xoff: usize,
3928    xdiv: usize,
3929    ys: &[B],
3930    yoff: usize,
3931    ydiv: usize,
3932    n: usize,
3933    tol: Tol,
3934    span: Span,
3935) -> Result<Vec<f64>> {
3936    par::try_fill(n, |start, part| {
3937        dyad_f64_chunk(op, xs, xoff, xdiv, ys, yoff, ydiv, start, part, tol, span)
3938    })
3939}
3940
3941/// One float pass over two buffers, each read in its own element type.
3942#[allow(clippy::too_many_arguments)]
3943fn float_dyad_data(
3944    op: ScalarDyad,
3945    x: &Data,
3946    xoff: usize,
3947    xdiv: usize,
3948    y: &Data,
3949    yoff: usize,
3950    ydiv: usize,
3951    n: usize,
3952    tol: Tol,
3953    span: Span,
3954) -> Result<Data> {
3955    let (mut tx, mut ty) = (Vec::new(), Vec::new());
3956    let out = f64_source!(x, tx, xs, {
3957        f64_source!(y, ty, ys, dyad_f64(op, xs, xoff, xdiv, ys, yoff, ydiv, n, tol, span)?)
3958    });
3959    Ok(Data::F64(out.into()))
3960}
3961
3962/// Whether two element types have nothing in common to compare: a
3963/// character against a number, or a box against either. Two numeric types
3964/// always meet somewhere, however far apart the widths are.
3965fn crossed_types(a: DType, b: DType) -> bool {
3966    let class = |d: DType| match d {
3967        DType::Box => 3,
3968        DType::Symbol => 2,
3969        DType::Char => 1,
3970        _ => 0,
3971    };
3972    class(a) != class(b)
3973}
3974
3975/// `x <. y` and `x >. y` over symbols: the smaller or larger NAME of the
3976/// pair, which is the only arithmetic a symbol has.
3977#[allow(clippy::too_many_arguments)]
3978fn symbol_min_max(
3979    op: ScalarDyad,
3980    x: &Data,
3981    xoff: usize,
3982    xdiv: usize,
3983    y: &Data,
3984    yoff: usize,
3985    ydiv: usize,
3986    n: usize,
3987    span: Span,
3988) -> Result<Data> {
3989    let (Data::Symbol(a), Data::Symbol(b)) = (x, y) else {
3990        return Err(symbol_arith(span));
3991    };
3992    let down = op == ScalarDyad::Min;
3993    let (out, _) = par::fill(n, |start, part: &mut [crate::symbol::Id]| {
3994        zip_chunk(a, xoff, xdiv, b, yoff, ydiv, start, part, |p, q, slot| {
3995            *slot = if crate::symbol::cmp(p, q).is_le() == down { p } else { q };
3996            true
3997        })
3998    });
3999    Ok(Data::Symbol(out.into()))
4000}
4001
4002#[allow(clippy::too_many_arguments)]
4003fn compare_data(
4004    op: ScalarDyad,
4005    x: &Data,
4006    xoff: usize,
4007    xdiv: usize,
4008    y: &Data,
4009    yoff: usize,
4010    ydiv: usize,
4011    n: usize,
4012    tol: Tol,
4013    span: Span,
4014) -> Result<Data> {
4015    use ScalarDyad::*;
4016    let (dx, dy) = (x.dtype(), y.dtype());
4017    let equality = matches!(op, Eq | Ne);
4018    // Equality is TOTAL across a character and a number in both
4019    // references: `'a' = 1` is 0. It is total across the BOX boundary in J
4020    // too — `(<1) = 1` is 0 — but not in APL, where a scalar verb reaches
4021    // inside the box instead, so that case falls through to the diagnostic
4022    // below rather than answering 0.
4023    let boxed = dx == DType::Box || dy == DType::Box;
4024    if equality && crossed_types(dx, dy) && (!boxed || tol.is_j()) {
4025        let unequal = op == Ne;
4026        return Ok(Data::Bool(vec![u8::from(unequal); n].into()));
4027    }
4028    if boxed {
4029        // Boxes have no order — J refuses `<` on them — but they do have
4030        // equality, which compares their contents.
4031        if !equality {
4032            return Err(box_arith(span));
4033        }
4034        let (Data::Box(a), Data::Box(b)) = (x, y) else {
4035            // Only APL reaches here: its scalar verbs pervade into a
4036            // nested argument, which is a promise rather than a refusal.
4037            return Err(Error::not_yet("a scalar function inside a nested array", span));
4038        };
4039        let (out, _) = par::fill(n, |start, part: &mut [u8]| {
4040            for (k, slot) in part.iter_mut().enumerate() {
4041                let i = start + k;
4042                let e = arrays_match(&a[xoff + i / xdiv], &b[yoff + i / ydiv], tol);
4043                *slot = u8::from(if op == Eq { e } else { !e });
4044            }
4045            true
4046        });
4047        return Ok(Data::Bool(out.into()));
4048    }
4049    if dx == DType::Symbol || dy == DType::Symbol {
4050        // Equality across the boundary answered above; anything else here
4051        // is an ordering that has nothing to order against.
4052        if dx != dy {
4053            return Err(Error::new(
4054                ErrorKind::Type,
4055                "cannot compare a symbol with data that is not a symbol",
4056                Some(span),
4057            ));
4058        }
4059        let (Data::Symbol(a), Data::Symbol(b)) = (x, y) else {
4060            return Err(Error::internal("symbol comparison on non-symbol data"));
4061        };
4062        // Ordering reads the names; equality is index against index.
4063        let (out, _) = par::fill(n, |start, part: &mut [u8]| {
4064            zip_chunk(a, xoff, xdiv, b, yoff, ydiv, start, part, |p, q, slot| {
4065                *slot = u8::from(match op {
4066                    Eq => p == q,
4067                    Ne => p != q,
4068                    _ => {
4069                        let o = crate::symbol::cmp(p, q);
4070                        match op {
4071                            Lt => o.is_lt(),
4072                            Le => o.is_le(),
4073                            Gt => o.is_gt(),
4074                            _ => o.is_ge(),
4075                        }
4076                    }
4077                });
4078                true
4079            })
4080        });
4081        return Ok(Data::Bool(out.into()));
4082    }
4083    if dx == DType::Char || dy == DType::Char {
4084        if dx != dy {
4085            return Err(Error::new(
4086                ErrorKind::Type,
4087                "cannot compare character and numeric data",
4088                Some(span),
4089            ));
4090        }
4091        if !equality {
4092            return Err(Error::new(
4093                ErrorKind::Type,
4094                "cannot order character data; only equality applies",
4095                Some(span),
4096            ));
4097        }
4098        let (Data::Char(a), Data::Char(b)) = (x, y) else {
4099            return Err(Error::internal("character comparison on non-character data"));
4100        };
4101        let (out, _) = par::fill(n, |start, part: &mut [u8]| {
4102            zip_chunk(a, xoff, xdiv, b, yoff, ydiv, start, part, |p, q, slot| {
4103                let e = p == q;
4104                *slot = if op == Eq { e as u8 } else { !e as u8 };
4105                true
4106            })
4107        });
4108        return Ok(Data::Bool(out.into()));
4109    }
4110    if DType::promote(dx, dy).is_some_and(DType::is_exact)
4111        && let Some(d) = exact_compare_data(op, x, xoff, xdiv, y, yoff, ydiv, n)
4112    {
4113        return Ok(d);
4114    }
4115    if dx == DType::Complex || dy == DType::Complex {
4116        if !equality {
4117            return Err(no_complex_order(span));
4118        }
4119        let (mut tx, mut ty) = (Vec::new(), Vec::new());
4120        let out = cx_source!(x, tx, xs, {
4121            cx_source!(y, ty, ys, {
4122                par::fill(n, |start, part: &mut [u8]| {
4123                    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, part, |a, b, slot| {
4124                        let e = tol.eq_cx(a.widen(), b.widen());
4125                        *slot = if op == Eq { e as u8 } else { !e as u8 };
4126                        true
4127                    })
4128                })
4129                .0
4130            })
4131        });
4132        return Ok(Data::Bool(out.into()));
4133    }
4134    // Floats compare with the dialect's tolerance; integers are exact
4135    // whatever it is, so the integer pass below is untouched by it.
4136    let out = if DType::promote(dx, dy) == Some(DType::F64) {
4137        let (mut tx, mut ty) = (Vec::<f64>::new(), Vec::<f64>::new());
4138        f64_source!(x, tx, xs, {
4139            f64_source!(y, ty, ys, {
4140                par::fill(n, |start, part: &mut [u8]| {
4141                    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, part, |a, b, slot| {
4142                        *slot = tol_cmp(op, a.widen(), b.widen(), tol) as u8;
4143                        true
4144                    })
4145                })
4146                .0
4147            })
4148        })
4149    } else {
4150        let (mut tx, mut ty) = (Vec::<i64>::new(), Vec::<i64>::new());
4151        i64_source!(x, tx, xs, {
4152            i64_source!(y, ty, ys, {
4153                par::fill(n, |start, part: &mut [u8]| {
4154                    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, part, |a, b, slot| {
4155                        let (a, b): (i64, i64) = (a.widen(), b.widen());
4156                        *slot = cmp_result(op, Some(i64::cmp(&a, &b))) as u8;
4157                        true
4158                    })
4159                })
4160                .0
4161            })
4162        })
4163    };
4164    Ok(Data::Bool(out.into()))
4165}
4166
4167/// One tolerant float comparison.
4168#[inline(always)]
4169pub(crate) fn tol_cmp(op: ScalarDyad, a: f64, b: f64, tol: Tol) -> bool {
4170    use ScalarDyad::*;
4171    match op {
4172        Eq => tol.eq(a, b),
4173        Ne => !tol.eq(a, b),
4174        Lt => tol.lt(a, b),
4175        Le => tol.le(a, b),
4176        Gt => tol.lt(b, a),
4177        Ge => tol.le(b, a),
4178        _ => false,
4179    }
4180}
4181
4182/// Two floats ordered under a tolerance: values that are tolerantly equal
4183/// tie, which is what leaves them in their original order in a stable sort.
4184/// A NaN ties with everything, which keeps the sort total.
4185#[inline]
4186pub(crate) fn tol_ord(a: f64, b: f64, tol: Tol) -> std::cmp::Ordering {
4187    use std::cmp::Ordering::Equal;
4188    if tol.ct != 0.0 && tol.eq(a, b) {
4189        return Equal;
4190    }
4191    a.partial_cmp(&b).unwrap_or(Equal)
4192}
4193
4194/// Turn an ordering (None for NaN) into a comparison result.
4195fn cmp_result(op: ScalarDyad, ord: Option<std::cmp::Ordering>) -> bool {
4196    use std::cmp::Ordering::*;
4197    use ScalarDyad::*;
4198    match ord {
4199        None => matches!(op, Ne),
4200        Some(o) => match op {
4201            Eq => o == Equal,
4202            Ne => o != Equal,
4203            Lt => o == Less,
4204            Le => o != Greater,
4205            Gt => o == Greater,
4206            Ge => o != Less,
4207            _ => false,
4208        },
4209    }
4210}
4211
4212/// Greatest common divisor, always nonnegative; `gcd(0, 0)` is 0.
4213///
4214/// GNU APL parts company here when one side is zero: `¯3∨0` and `0∨¯3` are
4215/// both `¯3` there, the other argument returned unchanged with its sign,
4216/// where J answers `3`. [`signed_gcd_i128`] is the APL reading.
4217fn gcd_i128(a: i128, b: i128) -> i128 {
4218    let (mut a, mut b) = (a.abs(), b.abs());
4219    while b != 0 {
4220        let t = a % b;
4221        a = b;
4222        b = t;
4223    }
4224    a
4225}
4226
4227/// GNU APL's GCD: the magnitude, except that a zero argument hands back
4228/// the other one untouched, sign and all. Only whole numbers keep the sign
4229/// — `¯3.5∨0` is `3.5` in GNU, so the real path below stays nonnegative.
4230fn signed_gcd_i128(a: i128, b: i128) -> i128 {
4231    match (a, b) {
4232        (0, _) => b,
4233        (_, 0) => a,
4234        _ => gcd_i128(a, b),
4235    }
4236}
4237
4238/// A finite float as `p / 10^s`, read off the shortest decimal that prints
4239/// back as this value — which is the number the user wrote and the number
4240/// both references show.
4241///
4242/// A value needing more than [`WRITTEN_DIGITS`] significant digits is not a
4243/// number anyone wrote: it is the residue of an arithmetic that missed, and
4244/// reading it as a decimal turns a rounding error into a divisor.
4245fn decimal_parts(v: f64) -> Option<(i128, u32)> {
4246    if !v.is_finite() {
4247        return None;
4248    }
4249    let text = format!("{v:e}");
4250    let (mantissa, exponent) = text.split_once('e')?;
4251    let exponent: i32 = exponent.parse().ok()?;
4252    let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
4253    if whole.trim_start_matches('-').len() + fraction.len() > WRITTEN_DIGITS {
4254        return None;
4255    }
4256    let mut digits: i128 = format!("{whole}{fraction}").parse().ok()?;
4257    let mut scale = fraction.len() as i32 - exponent;
4258    // A negative scale is a whole number with trailing zeros; fold them in
4259    // so every value arrives as `p / 10^s` with s at least zero.
4260    while scale < 0 {
4261        digits = digits.checked_mul(10)?;
4262        scale += 1;
4263    }
4264    // Beyond this the products below leave i128, and the Euclid fallback
4265    // takes over.
4266    (scale <= 34).then_some((digits, scale as u32))
4267}
4268
4269/// How many significant digits a decimal the user typed may need. Twelve
4270/// leaves every written constant intact and rejects the rounding residues:
4271/// `0.1+0.2` prints back as seventeen digits, `1.0000000000001` as fourteen.
4272const WRITTEN_DIGITS: usize = 12;
4273
4274/// The GCD of two reals read as the decimals they are printed as: `1.23`
4275/// and `4.56` are 123 and 456 hundredths, so their GCD is three hundredths.
4276/// That is the value both references print — theirs is the Euclid grind
4277/// that rounds to it, and a binary Euclid of our own cannot reach either.
4278fn gcd_decimal(a: f64, b: f64) -> Option<f64> {
4279    let (pa, sa) = decimal_parts(a)?;
4280    let (pb, sb) = decimal_parts(b)?;
4281    let scale = sa.max(sb);
4282    let lift = |p: i128, s: u32| 10i128.checked_pow(scale - s).and_then(|k| p.checked_mul(k));
4283    let g = gcd_i128(lift(pa, sa)?, lift(pb, sb)?);
4284    // Dividing through a decimal string keeps the one rounding the value
4285    // itself carries, where a multiply by 10^s of its own would add another.
4286    format!("{g}e-{scale}").parse().ok()
4287}
4288
4289/// The real GCD, by Euclid on the values themselves. Floats cannot reach an
4290/// exact zero remainder, so a remainder is taken to be zero once it is
4291/// within the comparison tolerance of the LARGER argument — the scale the
4292/// whole division sequence was measured against — or of the divisor, which
4293/// is the same step seen from the other end. That is what makes
4294/// `0.1 +. 0.2` answer `0.1` and `0.3 +. 0.1+0.2` answer `0.3` rather than
4295/// grinding down to a rounding error.
4296fn gcd_f64(a: f64, b: f64, tol: Tol) -> Option<f64> {
4297    let (mut a, mut b) = (a.abs(), b.abs());
4298    if !a.is_finite() || !b.is_finite() {
4299        return None;
4300    }
4301    let eps = tol.ct * a.max(b);
4302    // Euclid on reals converges as fast as it does on integers; the bound
4303    // is a guard, not the usual exit.
4304    for _ in 0..1000 {
4305        if b == 0.0 {
4306            return Some(a);
4307        }
4308        if a == 0.0 {
4309            return Some(b);
4310        }
4311        // The quotient's floor is TOLERANT, as J's `<.` is: a quotient a
4312        // rounding error below an integer is that integer, and the step
4313        // then lands on a remainder of zero instead of on the divisor. What
4314        // is left can only fall just outside [0, b), so it is clamped.
4315        let q = a / b;
4316        let mut k = q.floor();
4317        if tol.eq(q, k + 1.0) {
4318            k += 1.0;
4319        }
4320        let mut r = a - b * k;
4321        if r <= eps || tol.eq(r, b) {
4322            r = 0.0;
4323        }
4324        a = b;
4325        b = r;
4326    }
4327    Some(a)
4328}
4329
4330/// The real LCM/GCD pass: Euclid on the values, which is what J answers for
4331/// a pair that is not whole. An infinite operand has no answer, and both
4332/// references refuse it.
4333#[allow(clippy::too_many_arguments)]
4334fn real_lcm_gcd(
4335    op: ScalarDyad,
4336    xs: &[f64],
4337    xoff: usize,
4338    xdiv: usize,
4339    ys: &[f64],
4340    yoff: usize,
4341    ydiv: usize,
4342    n: usize,
4343    tol: Tol,
4344    gnu: bool,
4345    span: Span,
4346) -> Result<Data> {
4347    let mut out = vec![0.0f64; n];
4348    let mut ok = true;
4349    // GNU APL reads an operand within `⎕CT` of a whole number as that
4350    // number before anything else: `1.0000000000001∧5` is 5 there, not the
4351    // 5e13 the unrounded value grinds out. J does no such thing —
4352    // `1.0000000000001 +. 1` is `9.99e_14` in jconsole.
4353    let whole = |v: f64| {
4354        let w = v.round();
4355        if gnu && tol.eq(v, w) { w } else { v }
4356    };
4357    // And an operand no larger than `⎕CT` beside the other one is zero,
4358    // which leaves the other one: `1E¯13∨1` is 1 in GNU, not `1E¯13`.
4359    let vanishes = |v: f64, other: f64| gnu && v != 0.0 && v.abs() <= tol.ct * other.abs();
4360    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, 0, &mut out, |a, b, slot| {
4361        let (a, b) = (whole(a), whole(b));
4362        let (a, b) = (if vanishes(a, b) { 0.0 } else { a }, if vanishes(b, a) { 0.0 } else { b });
4363        let Some(g) = gcd_decimal(a, b).or_else(|| gcd_f64(a, b, tol)) else {
4364            ok = false;
4365            return false;
4366        };
4367        *slot = if op == ScalarDyad::Gcd {
4368            g
4369        } else if g == 0.0 {
4370            0.0
4371        } else {
4372            a / g * b
4373        };
4374        true
4375    });
4376    if !ok {
4377        return Err(Error::domain("LCM/GCD needs finite values", span));
4378    }
4379    Ok(Data::F64(out.into()))
4380}
4381
4382/// LCM/GCD over two buffers. Two booleans stay boolean, where the pair is
4383/// exactly logical and (LCM) / or (GCD); integers give integers; the real
4384/// GCD of fractions runs the same Euclid on the values themselves.
4385#[allow(clippy::too_many_arguments)]
4386fn lcm_gcd_data(
4387    op: ScalarDyad,
4388    x: &Data,
4389    xoff: usize,
4390    xdiv: usize,
4391    y: &Data,
4392    yoff: usize,
4393    ydiv: usize,
4394    n: usize,
4395    tol: Tol,
4396    rules: Rules,
4397    span: Span,
4398) -> Result<Data> {
4399    // GNU APL's GCD rounds its arguments and keeps a whole one's sign
4400    // beside a zero; J's and Dyalog's do neither.
4401    let gnu = rules.lang == crate::Lang::Apl
4402        && rules.gcd_rule == crate::frontend::GcdRule::Tolerant;
4403    let t = arith_type(x.dtype(), y.dtype(), span)?;
4404    if t == DType::Complex {
4405        // The Gaussian-integer versions, which is what both references give.
4406        return complex_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n, span);
4407    }
4408    if t.is_exact()
4409        && let Some(d) = exact_dyad_data(op, t, x, xoff, xdiv, y, yoff, ydiv, n, span)?
4410    {
4411        return Ok(d);
4412    }
4413    let both_bool = x.dtype() == DType::Bool && y.dtype() == DType::Bool;
4414    let float = t == DType::F64;
4415    let (xs, ys) = if float {
4416        let (mut tx, mut ty) = (Vec::new(), Vec::new());
4417        let xf = borrow_f64(x, &mut tx);
4418        let yf = borrow_f64(y, &mut ty);
4419        let integral = |v: &[f64]| v.iter().all(|&a| a.fract() == 0.0 && fits_i64(a));
4420        if !integral(xf) || !integral(yf) {
4421            return real_lcm_gcd(op, xf, xoff, xdiv, yf, yoff, ydiv, n, tol, gnu, span);
4422        }
4423        (
4424            xf.iter().map(|&a| a as i64).collect::<Vec<_>>(),
4425            yf.iter().map(|&a| a as i64).collect::<Vec<_>>(),
4426        )
4427    } else {
4428        let (mut tx, mut ty) = (Vec::new(), Vec::new());
4429        (borrow_i64(x, &mut tx).to_vec(), borrow_i64(y, &mut ty).to_vec())
4430    };
4431    // The chunk flag carries "every value fits an i64", so the whole pass
4432    // widens to float exactly when the sequential one would.
4433    let (out, fits) = par::fill(n, |start, part: &mut [i128]| {
4434        let mut fits = true;
4435        zip_chunk(&xs, xoff, xdiv, &ys, yoff, ydiv, start, part, |a, b, slot| {
4436            let (a, b) = (a as i128, b as i128);
4437            let g = if gnu { signed_gcd_i128(a, b) } else { gcd_i128(a, b) };
4438            let v = if op == ScalarDyad::Gcd {
4439                g
4440            } else if g == 0 {
4441                0
4442            } else {
4443                a / g * b
4444            };
4445            fits &= i64::try_from(v).is_ok();
4446            *slot = v;
4447            true
4448        });
4449        fits
4450    });
4451    if !fits || float {
4452        return Ok(Data::F64(par::map(&out, |&v| v as f64).into()));
4453    }
4454    if both_bool {
4455        return Ok(Data::Bool(par::map(&out, |&v| v as u8).into()));
4456    }
4457    Ok(Data::I64(par::map(&out, |&v| v as i64).into()))
4458}
4459
4460// ------------------------------------------------------- the exact types
4461
4462/// Numeric data widened to rationals. None for a type above the exact part
4463/// of the tower, which has no exact reading.
4464fn to_rat_vec(d: &Data) -> Option<Vec<Rat>> {
4465    Some(match d {
4466        Data::Bool(v) => v.iter().map(|&b| Rat::from_int(Ext::from(b))).collect(),
4467        Data::I64(v) => v.iter().map(|&x| Rat::from_int(Ext::from(x))).collect(),
4468        Data::Ext(v) => v.iter().map(|x| Rat::from_int(x.clone())).collect(),
4469        Data::Rat(v) => v.to_vec(),
4470        Data::F64(_) | Data::Complex(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => {
4471            return None;
4472        }
4473    })
4474}
4475
4476/// The elements one pass really reads, as rationals: indices
4477/// `off .. off + (n-1)/div`, rebased to zero.
4478///
4479/// A fold hands the SAME buffer to every step with a different offset, so
4480/// converting the whole of it each time would make the fold quadratic. The
4481/// window is the whole buffer in the ordinary elementwise case, and one
4482/// element in a fold step.
4483fn rat_window(d: &Data, off: usize, div: usize, n: usize) -> Option<Vec<Rat>> {
4484    if n == 0 {
4485        return Some(Vec::new());
4486    }
4487    let end = off + (n - 1) / div + 1;
4488    if off == 0 && end == d.len() {
4489        return to_rat_vec(d);
4490    }
4491    to_rat_vec(&d.slice(off, end))
4492}
4493
4494/// A finished exact pass as data: extended when the arguments were extended
4495/// AND every answer is whole, rational otherwise.
4496///
4497/// That one rule is the whole demotion story. It makes `4x % 2` extended and
4498/// `1x % 3` rational, and it leaves `1r2 - 1r2` rational even though the
4499/// answer is zero — a rational never falls back down the tower, which is
4500/// what the reference reports of it.
4501fn exact_data(t: DType, out: Vec<Rat>) -> Data {
4502    if t == DType::Ext && out.iter().all(Rat::is_integer) {
4503        return Data::Ext(out.iter().map(|r| r.to_int().expect("whole")).collect());
4504    }
4505    Data::Rat(out.into())
4506}
4507
4508/// The complaint a power too large to hold makes.
4509fn too_large(span: Span) -> Error {
4510    Error::domain(
4511        format!(
4512            "the exact result needs more than {} bits; use floats for a value this large",
4513            exact::MAX_BITS
4514        ),
4515        span,
4516    )
4517}
4518
4519/// `a ^ b` in the exact types. None when the answer is not exact — a
4520/// fractional exponent, or zero raised to a negative one.
4521fn exact_pow(a: &Rat, b: &Rat, span: Span) -> Result<Option<Rat>> {
4522    let Some(e) = b.to_int().as_ref().and_then(exact::ext_to_i64) else {
4523        return Ok(None);
4524    };
4525    if let Some(v) = a.pow(e) {
4526        return Ok(Some(v));
4527    }
4528    // `pow` declines for two reasons; only one of them is an error.
4529    if a.is_zero() && e < 0 { Ok(None) } else { Err(too_large(span)) }
4530}
4531
4532/// One elementwise dyadic pass in the exact types. `Ok(None)` means the
4533/// operation has no exact answer for these arguments, and the caller widens
4534/// to float exactly as it would for a machine integer that overflowed.
4535#[allow(clippy::too_many_arguments)]
4536fn exact_dyad_data(
4537    op: ScalarDyad,
4538    t: DType,
4539    x: &Data,
4540    xoff: usize,
4541    xdiv: usize,
4542    y: &Data,
4543    yoff: usize,
4544    ydiv: usize,
4545    n: usize,
4546    span: Span,
4547) -> Result<Option<Data>> {
4548    use ScalarDyad::*;
4549    let (Some(xs), Some(ys)) = (rat_window(x, xoff, xdiv, n), rat_window(y, yoff, ydiv, n))
4550    else {
4551        return Ok(None);
4552    };
4553    let mut out = Vec::with_capacity(n);
4554    for i in 0..n {
4555        let a = &xs[i / xdiv];
4556        let b = &ys[i / ydiv];
4557        let v = match op {
4558            Add => a.add(b),
4559            Sub => a.sub(b),
4560            Mul => a.mul(b),
4561            // A zero divisor is an infinity, which no rational spells.
4562            DivJ | DivApl => match a.div(b) {
4563                Some(v) => v,
4564                None => return Ok(None),
4565            },
4566            Min => a.min(b).clone(),
4567            Max => a.max(b).clone(),
4568            Residue => exact::rat_residue(a, b),
4569            Gcd => exact::rat_gcd(a, b),
4570            Lcm => exact::rat_lcm(a, b),
4571            Pow => match exact_pow(a, b, span)? {
4572                Some(v) => v,
4573                None => return Ok(None),
4574            },
4575            Binomial => match (a.to_int(), b.to_int()) {
4576                (Some(k), Some(m)) => match exact::ext_binomial(&k, &m) {
4577                    Some(v) => Rat::from_int(v),
4578                    None => return Ok(None),
4579                },
4580                _ => return Ok(None),
4581            },
4582            // An exact root exists only between whole numbers: the
4583            // reference answers `3 %: 8r27` with a float, not with `2r3`.
4584            Root if t == DType::Ext => {
4585                let (Some(k), Some(m)) = (a.to_int(), b.to_int()) else {
4586                    return Ok(None);
4587                };
4588                let Some(k) = exact::ext_to_i64(&k).and_then(|k| u32::try_from(k).ok()) else {
4589                    return Ok(None);
4590                };
4591                match exact::exact_root(k, &m) {
4592                    Some(v) => Rat::from_int(v),
4593                    None => return Ok(None),
4594                }
4595            }
4596            Root | Log | Circle | MakeComplex | PolarBy => return Ok(None),
4597            // Comparisons never reach here; `compare_data` takes them.
4598            Eq | Ne | Lt | Le | Gt | Ge => return Ok(None),
4599        };
4600        out.push(v);
4601    }
4602    Ok(Some(exact_data(t, out)))
4603}
4604
4605/// Elementwise monadic application in the exact types. `Ok(None)` widens to
4606/// float, as in the dyadic pass.
4607fn exact_monad(op: ScalarMonad, y: &Array) -> Option<Array> {
4608    use ScalarMonad::*;
4609    let v = to_rat_vec(&y.data)?;
4610    let shape = y.shape.clone();
4611    // The three that answer with a whole number whatever they were given:
4612    // `<. 7r2` is the extended 3, not the rational 3.
4613    if matches!(op, Floor | Ceil | Signum) {
4614        let out: Vec<Ext> = v
4615            .iter()
4616            .map(|r| match op {
4617                Floor => r.floor(),
4618                Ceil => r.ceil(),
4619                _ => r.signum(),
4620            })
4621            .collect();
4622        return Some(Array::new(shape, Data::Ext(out.into())).with_layout(y.layout()));
4623    }
4624    let two = Rat::from_int(Ext::from(2));
4625    let mut out = Vec::with_capacity(v.len());
4626    for r in &v {
4627        let value = match op {
4628            Conj => r.clone(),
4629            Neg => r.neg(),
4630            Abs => r.abs(),
4631            Recip => r.recip()?,
4632            Inc => r.add(&Rat::one()),
4633            Dec => r.sub(&Rat::one()),
4634            OneMinus => Rat::one().sub(r),
4635            Double => r.add(r),
4636            Halve => r.div(&two).expect("two is not zero"),
4637            Square => r.mul(r),
4638            Sqrt => r.sqrt()?,
4639            Factorial => Rat::from_int(r.to_int().as_ref().and_then(exact::ext_factorial)?),
4640            // No exact answer: the transcendentals, the two that make a
4641            // complex value, and logical negation.
4642            Exp | Ln | Pi | Imaginary | Polar | Not => return None,
4643            Floor | Ceil | Signum => unreachable!("handled above"),
4644        };
4645        out.push(value);
4646    }
4647    Some(Array::new(shape, exact_data(y.dtype(), out)).with_layout(y.layout()))
4648}
4649
4650/// `x: y`: the argument in the exact types. Whole values become extended
4651/// integers; anything else becomes the simplest rational within the
4652/// dialect's comparison tolerance of it, so `x: 0.1` is `1r10` rather than
4653/// the binary fraction a double really holds.
4654fn to_exact(y: &Array, span: Span) -> Result<Array> {
4655    let data = match &y.data {
4656        Data::Ext(_) | Data::Rat(_) => return Ok(y.clone()),
4657        Data::Bool(v) => Data::Ext(v.iter().map(|&b| Ext::from(b)).collect()),
4658        Data::I64(v) => Data::Ext(v.iter().map(|&x| Ext::from(x)).collect()),
4659        Data::F64(v) => {
4660            let mut out = Vec::with_capacity(v.len());
4661            for &x in v.iter() {
4662                out.push(exact::f64_to_rat(x).ok_or_else(|| {
4663                    Error::domain("an infinity has no exact value", span)
4664                })?);
4665            }
4666            exact_data(DType::Ext, out)
4667        }
4668        Data::Complex(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => {
4669            return Err(Error::domain(
4670                format!("x: needs real numbers, not {} data", y.dtype().name()),
4671                span,
4672            ));
4673        }
4674    };
4675    Ok(Array::new(y.shape.clone(), data).with_layout(y.layout()))
4676}
4677
4678/// `_1 x: y`: an exact value back as a machine number — an extended integer
4679/// as an integer where it fits, a rational as a float.
4680fn from_exact(y: &Array) -> Array {
4681    let shape = y.shape.clone();
4682    match &y.data {
4683        Data::Ext(v) => match v.iter().map(exact::ext_to_i64).collect::<Option<Vec<i64>>>() {
4684            Some(out) => Array::new(shape, Data::I64(out.into())).with_layout(y.layout()),
4685            None => Array::new(shape, Data::F64(v.iter().map(exact::ext_to_f64).collect()))
4686                .with_layout(y.layout()),
4687        },
4688        Data::Rat(v) => Array::new(shape, Data::F64(v.iter().map(Rat::to_f64).collect()))
4689            .with_layout(y.layout()),
4690        _ => y.clone(),
4691    }
4692}
4693
4694/// `x x: y`: the exact form named by x.
4695fn exact_form(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
4696    match one_whole(x, "the form x: converts to", near, span)? {
4697        1 => {
4698            let e = to_exact(y, span)?;
4699            e.cast(DType::Rat).ok_or_else(|| Error::internal("an exact value has no rational form"))
4700        }
4701        2 => {
4702            let e = to_exact(y, span)?;
4703            let v = to_rat_vec(&e.data).ok_or_else(|| Error::internal("x: gave an inexact value"))?;
4704            let mut out = Vec::with_capacity(2 * v.len());
4705            for r in &v {
4706                out.push(r.numer().clone());
4707                out.push(r.denom().clone());
4708            }
4709            let mut shape = y.shape.clone();
4710            shape.push(2);
4711            Ok(Array::new(shape, Data::Ext(out.into())))
4712        }
4713        -1 => Ok(from_exact(y)),
4714        // The one that leaves an inexact argument alone.
4715        -2 => {
4716            if !y.dtype().is_numeric() {
4717                return Err(Error::domain(
4718                    format!("x: needs real numbers, not {} data", y.dtype().name()),
4719                    span,
4720                ));
4721            }
4722            Ok(y.clone())
4723        }
4724        n => Err(Error::domain(
4725            format!("x: converts to form 1, 2, _1 or _2, not {n}"),
4726            span,
4727        )),
4728    }
4729}
4730
4731/// Exact comparison of two exact buffers. No tolerance applies: two exact
4732/// values are equal when they are the same number, which is why
4733/// `(10x^30) = 1 + 10x^30` is 0 where the float answer would be 1.
4734#[allow(clippy::too_many_arguments)]
4735fn exact_compare_data(
4736    op: ScalarDyad,
4737    x: &Data,
4738    xoff: usize,
4739    xdiv: usize,
4740    y: &Data,
4741    yoff: usize,
4742    ydiv: usize,
4743    n: usize,
4744) -> Option<Data> {
4745    let (xs, ys) = (rat_window(x, xoff, xdiv, n)?, rat_window(y, yoff, ydiv, n)?);
4746    let out: Vec<u8> = (0..n)
4747        .map(|i| {
4748            let ord = xs[i / xdiv].cmp(&ys[i / ydiv]);
4749            cmp_result(op, Some(ord)) as u8
4750        })
4751        .collect();
4752    Some(Data::Bool(out.into()))
4753}
4754
4755/// One elementwise dyadic pass over two buffers. Element `i` of the result
4756/// pairs `x[xoff + i / xdiv]` with `y[yoff + i / ydiv]`, so broadcasting and
4757/// folding both run without materialising cells.
4758#[allow(clippy::too_many_arguments)]
4759fn scalar_dyad_data(
4760    op: ScalarDyad,
4761    x: &Data,
4762    xoff: usize,
4763    xdiv: usize,
4764    y: &Data,
4765    yoff: usize,
4766    ydiv: usize,
4767    n: usize,
4768    tol: Tol,
4769    rules: Rules,
4770    span: Span,
4771) -> Result<Data> {
4772    use ScalarDyad::*;
4773    if x.dtype() == DType::Symbol || y.dtype() == DType::Symbol {
4774        match op {
4775            // Comparison takes the path below, which knows symbols.
4776            Eq | Ne | Lt | Le | Gt | Ge => {}
4777            // `<.` and `>.` are the smaller and the larger of two names,
4778            // and a name has an order, so they answer a symbol.
4779            Min | Max => {
4780                return symbol_min_max(op, x, xoff, xdiv, y, yoff, ydiv, n, span);
4781            }
4782            _ => return Err(symbol_arith(span)),
4783        }
4784    }
4785    if matches!(op, Eq | Ne | Lt | Le | Gt | Ge) {
4786        return compare_data(op, x, xoff, xdiv, y, yoff, ydiv, n, tol, span);
4787    }
4788    if matches!(op, Lcm | Gcd) {
4789        return lcm_gcd_data(op, x, xoff, xdiv, y, yoff, ydiv, n, tol, rules, span);
4790    }
4791    let t = arith_type(x.dtype(), y.dtype(), span)?;
4792    if t.is_exact()
4793        && let Some(d) = exact_dyad_data(op, t, x, xoff, xdiv, y, yoff, ydiv, n, span)?
4794    {
4795        return Ok(d);
4796    }
4797    // No exact answer above: widen, exactly as an integer overflow does.
4798    if t == DType::I64 && !matches!(op, DivJ | DivApl | Log | Root | Circle) {
4799        // Binomial reaches this path: a whole pair has a whole answer, and
4800        // the i64 step declines (None) exactly where J widens to float.
4801        if let Some(d) = int_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n) {
4802            return Ok(d);
4803        }
4804        // Integer overflow (or a fractional result): J widens to float.
4805    }
4806    if t == DType::Complex
4807        || matches!(op, MakeComplex | PolarBy)
4808        || pass_leaves_reals(op, x, xoff, xdiv, y, yoff, ydiv, n)
4809    {
4810        let data = complex_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n, span)?;
4811        // GNU APL has no infinite logarithm in the complex domain either:
4812        // `¯1⍟0` is a DOMAIN ERROR there, exactly as `2⍟0` is on the reals,
4813        // and the real path above already refuses that one.
4814        if op == Log
4815            && rules.lang == crate::Lang::Apl
4816            && let Data::Complex(v) = &data
4817            && v.iter().any(|z| !z[0].is_finite() || !z[1].is_finite())
4818        {
4819            return Err(Error::domain("this logarithm has no value", span));
4820        }
4821        if op == Circle && circle_reads_a_part(x, xoff, xdiv, n) && let Data::Complex(v) = &data {
4822            return Ok(Data::F64(v.iter().map(|z| z[0]).collect()));
4823        }
4824        return Ok(data);
4825    }
4826    float_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n, tol, span)
4827}
4828
4829/// Elementwise dyadic application of a scalar operation to whole arrays.
4830/// Frame the results of a pervading scalar function. Cells that all came
4831/// back simple scalars make a simple array again — `(1 2)+(3 4)` is a plain
4832/// vector — and anything else is enclosed, which is what keeps the nesting.
4833fn frame_pervaded(frame: Vec<usize>, cells: Vec<Array>, span: Span) -> Result<Array> {
4834    if cells.iter().all(|c| c.rank() == 0 && c.dtype() != DType::Box) {
4835        return assemble(&frame, cells, span);
4836    }
4837    let boxes: Vec<Array> = cells.into_iter().collect();
4838    Ok(Array::new(frame, Data::Box(boxes.into())))
4839}
4840
4841/// APL's scalar functions PERVADE a nested argument: they descend through
4842/// the boxes, item by item, and apply to the simple values at the bottom.
4843/// The two sides agree by the ordinary scalar rule at every level, so a
4844/// scalar spreads over a nested array's items as it does over a simple
4845/// array's elements. J has no such rule — a box there is a type error.
4846fn pervade_dyad(
4847    op: ScalarDyad,
4848    x: &Array,
4849    y: &Array,
4850    cfg: EvalCfg,
4851    span: Span,
4852) -> Result<Array> {
4853    let p = agree(&x.shape, &y.shape, &x.shape, &y.shape, cfg.agreement, span)?;
4854    if p.n == 0 {
4855        return Ok(Array::new(p.frame, Data::empty(DType::Box)));
4856    }
4857    let (xr, yr) = (x.to_row_major(), y.to_row_major());
4858    let mut cells = Vec::with_capacity(p.n);
4859    for i in 0..p.n {
4860        let a = open_cell(&atom(&xr, i / p.x_div));
4861        let b = open_cell(&atom(&yr, i / p.y_div));
4862        cells.push(scalar_dyad(op, &a, &b, cfg, span)?);
4863    }
4864    frame_pervaded(p.frame, cells, span)
4865}
4866
4867/// The monadic half of [`pervade_dyad`].
4868fn pervade_monad(op: ScalarMonad, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
4869    if y.count() == 0 {
4870        return Ok(Array::new(y.shape.clone(), Data::empty(DType::Box)));
4871    }
4872    let yr = y.to_row_major();
4873    let mut cells = Vec::with_capacity(y.count());
4874    for i in 0..y.count() {
4875        let a = open_cell(&atom(&yr, i));
4876        cells.push(scalar_monad(op, &a, cfg, span)?);
4877    }
4878    frame_pervaded(y.shape.clone(), cells, span)
4879}
4880
4881/// The same array with its complex values read as the reals they are, or
4882/// `None` when one of them really is complex. A value that is not complex
4883/// at all needs no reading and answers for itself.
4884fn as_real(a: &Array) -> Option<Array> {
4885    if a.dtype() != DType::Complex {
4886        return Some(a.clone());
4887    }
4888    let real: Option<Vec<f64>> = a.to_f64_vec();
4889    Some(Array::new(a.shape.clone(), Data::F64(real?.into())))
4890}
4891
4892fn scalar_dyad(
4893    op: ScalarDyad,
4894    x: &Array,
4895    y: &Array,
4896    cfg: EvalCfg,
4897    span: Span,
4898) -> Result<Array> {
4899    if cfg.rules.lang == crate::Lang::Apl
4900        && (x.dtype() == DType::Box || y.dtype() == DType::Box)
4901    {
4902        return pervade_dyad(op, x, y, cfg, span);
4903    }
4904    // A complex value with no imaginary part is ordered by the real it
4905    // displays as: J answers `1 <. j. 0` with 0 and `3j0 < 4` with 1, while
4906    // `3!:0 j. 0` still reports the complex type. Only the ordering verbs
4907    // read a value that way — arithmetic keeps the complex type through its
4908    // answer, which is why the demotion sits here and not in the maker.
4909    if matches!(op, ScalarDyad::Min | ScalarDyad::Max | ScalarDyad::Lt
4910        | ScalarDyad::Le | ScalarDyad::Gt | ScalarDyad::Ge)
4911        && (x.dtype() == DType::Complex || y.dtype() == DType::Complex)
4912        && let (Some(a), Some(b)) = (as_real(x), as_real(y))
4913    {
4914        return scalar_dyad(op, &a, &b, cfg, span);
4915    }
4916    let p = agree(&x.shape, &y.shape, &x.shape, &y.shape, cfg.agreement, span)?;
4917    // Nothing to apply the verb to: `'a' + ''` is an empty, not a type
4918    // error, because no pair of elements was ever formed. The agreement
4919    // above still holds — `1 2 3 + ''` is a length error either way.
4920    if p.n == 0 {
4921        return Ok(Array::new(p.frame, Data::empty(empty_result_type(x, y))));
4922    }
4923    let data = scalar_dyad_data(
4924        op,
4925        &x.data,
4926        0,
4927        p.x_div,
4928        &y.data,
4929        0,
4930        p.y_div,
4931        p.n,
4932        cfg.tol,
4933        cfg.rules,
4934        span,
4935    )?;
4936    Ok(Array::new(p.frame, data))
4937}
4938
4939/// The element type of an empty answer. A numeric operand names it; with
4940/// none, the numbers an arithmetic result would have held.
4941fn empty_result_type(x: &Array, y: &Array) -> DType {
4942    for a in [x, y] {
4943        if a.dtype().is_numeric() {
4944            return a.dtype();
4945        }
4946    }
4947    DType::I64
4948}
4949
4950/// Is `v` exactly representable as an i64?
4951fn fits_i64(v: f64) -> bool {
4952    v.is_finite() && v >= i64::MIN as f64 && v < i64::MAX as f64
4953}
4954
4955/// Does a real argument have no real answer under this monad?
4956fn monad_leaves_reals(op: ScalarMonad, d: &Data) -> bool {
4957    use ScalarMonad::*;
4958    match op {
4959        // The two that make a complex number out of a real one.
4960        Imaginary | Polar => d.dtype().is_numeric(),
4961        Sqrt | Ln => match d {
4962            Data::I64(v) => par::any(v, |&x| x < 0),
4963            Data::F64(v) => par::any(v, |&x| x < 0.0),
4964            Data::Ext(v) => v.iter().any(|x| x.sign() == num_bigint::Sign::Minus),
4965            Data::Rat(v) => v.iter().any(|x| x < &Rat::zero()),
4966            _ => false,
4967        },
4968        _ => false,
4969    }
4970}
4971
4972/// Elementwise monadic application in the complex domain.
4973fn complex_monad(op: ScalarMonad, y: &Array, span: Span) -> Result<Array> {
4974    use ScalarMonad::*;
4975    let mut tmp = Vec::new();
4976    let v = borrow_cx(&y.data, &mut tmp);
4977    if y.count() > 0 && v.is_empty() {
4978        return Err(wrong_type(y.dtype(), span));
4979    }
4980    let data = match op {
4981        // Magnitude is the one that leaves the complex domain again.
4982        Abs => Data::F64(par::map(v, |&z| cx::abs(z)).into()),
4983        Not => return Err(Error::domain("logical negation needs values of 0 or 1", span)),
4984        Factorial => {
4985            return Err(Error::not_yet("the factorial of a complex number", span));
4986        }
4987        _ => {
4988            let step: fn(Cx) -> Cx = match op {
4989                Conj => cx::conj,
4990                Neg => cx::neg,
4991                Signum => cx::signum,
4992                Recip => cx::recip,
4993                Sqrt => cx::sqrt,
4994                Exp => cx::exp,
4995                Ln => cx::ln,
4996                Floor => cx::floor,
4997                Ceil => cx::ceil,
4998                OneMinus => |z| cx::sub(cx::ONE, z),
4999                Inc => |z| cx::add(z, cx::ONE),
5000                Dec => |z| cx::sub(z, cx::ONE),
5001                Double => |z| cx::add(z, z),
5002                Halve => |z| [z[0] / 2.0, z[1] / 2.0],
5003                Square => |z| cx::mul(z, z),
5004                Pi => |z| [std::f64::consts::PI * z[0], std::f64::consts::PI * z[1]],
5005                Imaginary => |z| cx::mul(cx::I, z),
5006                Polar => |z| cx::exp(cx::mul(cx::I, z)),
5007                Abs | Not | Factorial => unreachable!("handled above"),
5008            };
5009            Data::Complex(par::map(v, |&z| step(z)).into())
5010        }
5011    };
5012    Ok(Array::new(y.shape.clone(), data).with_layout(y.layout()))
5013}
5014
5015/// Elementwise monadic application to a whole array.
5016fn scalar_monad(op: ScalarMonad, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
5017    use ScalarMonad::*;
5018    if cfg.rules.lang == crate::Lang::Apl && y.dtype() == DType::Box {
5019        return pervade_monad(op, y, cfg, span);
5020    }
5021    let tol = cfg.tol;
5022    let d = &y.data;
5023    // An empty argument has no element for the verb to run on, so its type
5024    // never comes up: `%: ''` is an empty, not a type error.
5025    if y.count() == 0 && !d.dtype().is_numeric() {
5026        return Ok(Array::new(y.shape.clone(), Data::empty(DType::I64)));
5027    }
5028    if d.dtype() == DType::Complex || monad_leaves_reals(op, d) {
5029        return complex_monad(op, y, span);
5030    }
5031    if d.dtype().is_exact() && let Some(a) = exact_monad(op, y) {
5032        return Ok(a);
5033    }
5034    // No exact answer above: the float pass below takes over.
5035    // The float-only operations borrow float data as it lies; anything else
5036    // is widened once into `tmp` first.
5037    let mut tmp = Vec::new();
5038    let data = match op {
5039        // Conjugation is the identity on reals.
5040        Conj if d.dtype().is_numeric() => d.clone(),
5041        Conj => return Err(wrong_type(d.dtype(), span)),
5042        // Both make a complex value out of any argument, so they never
5043        // reach the real path.
5044        Imaginary | Polar => return Err(Error::internal("a complex monad on the real path")),
5045        Neg => match d {
5046            Data::Bool(v) => Data::I64(par::map(v, |&b| -(b as i64)).into()),
5047            Data::I64(v) => match par::try_map(v, i64::checked_neg) {
5048                Some(out) => Data::I64(out.into()),
5049                None => Data::F64(par::map(v, |&x| -(x as f64)).into()),
5050            },
5051            Data::F64(v) => Data::F64(par::map(v, |&x| -x).into()),
5052            _ => return Err(wrong_type(d.dtype(), span)),
5053        },
5054        Signum => match d {
5055            Data::Bool(v) => Data::I64(par::map(v, |&b| b as i64).into()),
5056            Data::I64(v) => Data::I64(par::map(v, |&x| x.signum()).into()),
5057            // NaN has no sign here; it yields 0, and so does anything the
5058            // dialect's tolerance reads as zero.
5059            Data::F64(v) => Data::F64(
5060                par::map(v, |&x| {
5061                    if tol.is_zero(x) {
5062                        0.0
5063                    } else if x > 0.0 {
5064                        1.0
5065                    } else if x < 0.0 {
5066                        -1.0
5067                    } else {
5068                        0.0
5069                    }
5070                })
5071                .into(),
5072            ),
5073            _ => return Err(wrong_type(d.dtype(), span)),
5074        },
5075        Recip => {
5076            // `% 0` is infinity in J. GNU APL has no such value: `÷0` is a
5077            // DOMAIN ERROR, as its dyadic `2÷0` already is here, and the
5078            // monad has to refuse the same pair the dyad does — including
5079            // through `¨`, `/` and `\`, which all arrive at this one step.
5080            let v = as_f64(d, &mut tmp, span)?;
5081            if !tol.is_j() && par::any(v, |&x| x == 0.0) {
5082                return Err(Error::domain("zero has no reciprocal", span));
5083            }
5084            Data::F64(par::map(v, |&x| if x == 0.0 { f64::INFINITY } else { 1.0 / x }).into())
5085        }
5086        Sqrt => {
5087            // A negative value went to the complex path before this point.
5088            let v = as_f64(d, &mut tmp, span)?;
5089            Data::F64(par::map(v, |&x| x.sqrt()).into())
5090        }
5091        Exp => {
5092            let v = as_f64(d, &mut tmp, span)?;
5093            Data::F64(par::map(v, |&x| x.exp()).into())
5094        }
5095        Abs => match d {
5096            Data::Bool(_) => d.clone(),
5097            Data::I64(v) => match par::try_map(v, i64::checked_abs) {
5098                Some(out) => Data::I64(out.into()),
5099                None => Data::F64(par::map(v, |&x| (x as f64).abs()).into()),
5100            },
5101            Data::F64(v) => Data::F64(par::map(v, |&x| x.abs()).into()),
5102            _ => return Err(wrong_type(d.dtype(), span)),
5103        },
5104        Floor | Ceil => match d {
5105            Data::Bool(v) => Data::I64(par::map(v, |&b| b as i64).into()),
5106            Data::I64(_) => d.clone(),
5107            Data::F64(v) => {
5108                let round = |x: f64| if op == Floor { tol.floor(x) } else { tol.ceil(x) };
5109                // Integer when every rounded value is one, as in J.
5110                match par::try_map(v, |x| {
5111                    let r = round(x);
5112                    fits_i64(r).then_some(r as i64)
5113                }) {
5114                    Some(out) => Data::I64(out.into()),
5115                    None => Data::F64(par::map(v, |&x| round(x)).into()),
5116                }
5117            }
5118            _ => return Err(wrong_type(d.dtype(), span)),
5119        },
5120        Inc | Dec => {
5121            let step = if op == Inc { 1i64 } else { -1 };
5122            match d {
5123                Data::Bool(v) => Data::I64(par::map(v, |&b| b as i64 + step).into()),
5124                Data::I64(v) => match par::try_map(v, |x: i64| x.checked_add(step)) {
5125                    Some(out) => Data::I64(out.into()),
5126                    None => Data::F64(par::map(v, |&x| x as f64 + step as f64).into()),
5127                },
5128                Data::F64(v) => Data::F64(par::map(v, |&x| x + step as f64).into()),
5129                _ => return Err(wrong_type(d.dtype(), span)),
5130            }
5131        }
5132        Double | Square => match d {
5133            Data::Bool(v) => {
5134                Data::I64(par::map(v, |&b| if op == Double { 2 * b as i64 } else { b as i64 }).into())
5135            }
5136            Data::I64(v) => {
5137                let f = |x: i64| if op == Double { x.checked_mul(2) } else { x.checked_mul(x) };
5138                match par::try_map(v, f) {
5139                    Some(out) => Data::I64(out.into()),
5140                    None => Data::F64(
5141                        par::map(v, |&x| {
5142                            let x = x as f64;
5143                            if op == Double { x + x } else { x * x }
5144                        })
5145                        .into(),
5146                    ),
5147                }
5148            }
5149            Data::F64(v) => {
5150                Data::F64(par::map(v, |&x| if op == Double { x + x } else { x * x }).into())
5151            }
5152            _ => return Err(wrong_type(d.dtype(), span)),
5153        },
5154        Halve => {
5155            let v = as_f64(d, &mut tmp, span)?;
5156            Data::F64(par::map(v, |&x| x / 2.0).into())
5157        }
5158        Pi => {
5159            let v = as_f64(d, &mut tmp, span)?;
5160            Data::F64(par::map(v, |&x| std::f64::consts::PI * x).into())
5161        }
5162        Factorial => {
5163            let v = as_f64(d, &mut tmp, span)?;
5164            let out = par::map(v, |&x| factorial_as(x, tol));
5165            if tol.is_j() {
5166                // The one factorial J refuses. Everything else its gamma
5167                // cannot reach it answers with `_`, which `factorial_as`
5168                // has already done.
5169                if v.iter().zip(&out).any(|(&x, &r)| tol.made_nan(r, x, 0.0)) {
5170                    return Err(Error::nan("`! __` has no value", span));
5171                }
5172            } else if par::any(&out, |v: &f64| !v.is_finite()) {
5173                // GNU APL refuses every factorial without a value: `!¯3`
5174                // and `!¯1` sit on a pole of the gamma function, `!171` has
5175                // overflowed it. J answers all three with `_`.
5176                return Err(Error::domain("this factorial has no value", span));
5177            }
5178            Data::F64(out.into())
5179        }
5180        Ln => {
5181            // As with `Sqrt`: a negative value is already on the complex path.
5182            let v = as_f64(d, &mut tmp, span)?;
5183            // ln(0) is negative infinity, which is what J prints as __. GNU
5184            // APL has no such value and refuses `⍟0`, exactly as it
5185            // refuses `÷0`.
5186            if !tol.is_j() && par::any(v, |&x| x == 0.0) {
5187                return Err(Error::domain("zero has no logarithm", span));
5188            }
5189            Data::F64(par::map(v, |&x| x.ln()).into())
5190        }
5191        OneMinus => match d {
5192            Data::Bool(v) => Data::Bool(par::map(v, |&b| 1 - b).into()),
5193            Data::I64(v) => match par::try_map(v, |x: i64| 1i64.checked_sub(x)) {
5194                Some(out) => Data::I64(out.into()),
5195                None => Data::F64(par::map(v, |&x| 1.0 - x as f64).into()),
5196            },
5197            Data::F64(v) => Data::F64(par::map(v, |&x| 1.0 - x).into()),
5198            _ => return Err(wrong_type(d.dtype(), span)),
5199        },
5200        Not => {
5201            let bad = || Error::domain("logical negation needs values of 0 or 1", span);
5202            match d {
5203                Data::Bool(v) => Data::Bool(par::map(v, |&b| 1 - b).into()),
5204                Data::I64(v) => {
5205                    let out = par::try_map(v, |x: i64| match x {
5206                        0 => Some(1u8),
5207                        1 => Some(0u8),
5208                        _ => None,
5209                    })
5210                    .ok_or_else(bad)?;
5211                    Data::Bool(out.into())
5212                }
5213                Data::F64(v) => {
5214                    let out = par::try_map(v, |x: f64| {
5215                        if x == 0.0 {
5216                            Some(1u8)
5217                        } else if x == 1.0 {
5218                            Some(0u8)
5219                        } else {
5220                            None
5221                        }
5222                    })
5223                    .ok_or_else(bad)?;
5224                    Data::Bool(out.into())
5225                }
5226                _ => return Err(bad()),
5227            }
5228        }
5229    };
5230    Ok(Array::new(y.shape.clone(), data).with_layout(y.layout()))
5231}
5232
5233// -------------------------------------------------- structural operations
5234
5235/// Reverse the axes.
5236///
5237/// Nothing moves: reversing every axis is exactly what reading the same
5238/// buffer in the other layout does, so this is a reversed shape, the same
5239/// buffer, and the flag flipped. Whatever reads the result either knows
5240/// both layouts or is handed the rows, materialised once and only if some
5241/// verb really needs them.
5242fn transpose_axes(y: &Array) -> Array {
5243    if y.rank() < 2 {
5244        return y.clone();
5245    }
5246    let out_shape: Vec<usize> = y.shape.iter().rev().copied().collect();
5247    let flipped = match y.layout() {
5248        Layout::RowMajor => Layout::ColMajor,
5249        Layout::ColMajor => Layout::RowMajor,
5250    };
5251    Array::new(out_shape, y.data.clone()).with_layout(flipped)
5252}
5253
5254/// J `i.`: an ascending sequence laid out in shape |y|, running backwards
5255/// along every axis whose given length was negative.
5256fn iota_j(y: &Array, near: NearInt, span: Span) -> Result<Array> {
5257    if y.rank() > 1 {
5258        return Err(Error::new(
5259            ErrorKind::Rank,
5260            "index generator needs a scalar or vector argument",
5261            Some(span),
5262        ));
5263    }
5264    let dims = y
5265        .to_i64_vec_near(near)
5266        .ok_or_else(|| Error::domain("index generator needs integer lengths", span))?;
5267    let shape: Vec<usize> = dims.iter().map(|d| d.unsigned_abs() as usize).collect();
5268    let n = crate::limits::elements(&shape, span)?;
5269    let st = strides(&shape);
5270    let mut out = Vec::with_capacity(n);
5271    let mut coord = vec![0usize; shape.len()];
5272    for _ in 0..n {
5273        let mut v = 0usize;
5274        for k in 0..shape.len() {
5275            let c = if dims[k] < 0 { shape[k] - 1 - coord[k] } else { coord[k] };
5276            v += c * st[k];
5277        }
5278        out.push(v as i64);
5279        odometer(&mut coord, &shape);
5280    }
5281    let data = Data::I64(out.into());
5282    // An extended length generates extended indices, so `*/ >: i. 25x` is
5283    // the exact factorial rather than the overflowing machine one.
5284    let data = if y.dtype() == DType::Ext {
5285        data.cast(DType::Ext).ok_or_else(|| Error::internal("integers have no extended form"))?
5286    } else {
5287        data
5288    };
5289    Ok(Array::new(shape, data))
5290}
5291
5292/// The first item, or a cell of fills when there are no items.
5293fn head(y: &Array) -> Array {
5294    if y.rank() == 0 {
5295        return y.clone();
5296    }
5297    if y.items() == 0 {
5298        let cell_shape = y.shape[1..].to_vec();
5299        let n: usize = cell_shape.iter().product();
5300        return Array::new(cell_shape, fill_data(y.dtype(), n));
5301    }
5302    y.item(0)
5303}
5304
5305fn behead(y: &Array, span: Span) -> Result<Array> {
5306    if y.rank() == 0 {
5307        return Err(Error::domain("cannot drop the first item of a scalar", span));
5308    }
5309    if y.items() == 0 {
5310        return Ok(y.clone());
5311    }
5312    let m = y.item_size();
5313    let mut shape = y.shape.clone();
5314    shape[0] -= 1;
5315    Ok(Array::new(shape, y.data.slice(m, y.count())))
5316}
5317
5318/// The last item, or a cell of fills when there are no items.
5319fn tail(y: &Array) -> Array {
5320    if y.rank() == 0 {
5321        return y.clone();
5322    }
5323    let n = y.items();
5324    if n == 0 {
5325        let cell_shape = y.shape[1..].to_vec();
5326        let m: usize = cell_shape.iter().product();
5327        return Array::new(cell_shape, fill_data(y.dtype(), m));
5328    }
5329    y.item(n - 1)
5330}
5331
5332/// All items but the last. A scalar has one item, so it curtails to empty.
5333fn curtail(y: &Array) -> Array {
5334    if y.rank() == 0 {
5335        return Array::empty(y.dtype());
5336    }
5337    let n = y.items();
5338    if n == 0 {
5339        return y.clone();
5340    }
5341    let m = y.item_size();
5342    let mut shape = y.shape.clone();
5343    shape[0] = n - 1;
5344    Array::new(shape, y.data.slice(0, (n - 1) * m))
5345}
5346
5347/// Reverse the items (the leading axis).
5348fn reverse(y: &Array) -> Array {
5349    if y.rank() == 0 {
5350        return y.clone();
5351    }
5352    let n = y.items();
5353    let m = y.item_size();
5354    let mut data = Data::empty(y.dtype());
5355    for i in (0..n).rev() {
5356        for k in 0..m {
5357            push_elem(&mut data, &y.data, i * m + k);
5358        }
5359    }
5360    Array::new(y.shape.clone(), data)
5361}
5362
5363/// `x |. y`: rotate axis k of y left by `x[k]`, cyclically; a negative
5364/// amount rotates right. A scalar argument has nothing to rotate.
5365fn rotate(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
5366    let counts = axis_counts(x, "rotate", near, span)?;
5367    if y.rank() == 0 {
5368        return Ok(y.clone());
5369    }
5370    if counts.len() > y.rank() {
5371        return Err(Error::new(
5372            ErrorKind::Length,
5373            format!(
5374                "rotate has {} amounts for an argument of rank {}",
5375                counts.len(),
5376                y.rank()
5377            ),
5378            Some(span),
5379        ));
5380    }
5381    let st = strides(&y.shape);
5382    let n = y.count();
5383    let r = y.rank();
5384    let mut data = Data::empty(y.dtype());
5385    let mut coord = vec![0usize; r];
5386    for _ in 0..n {
5387        let mut idx = 0usize;
5388        for k in 0..r {
5389            // No axis is empty here: an empty axis makes n zero.
5390            let len = y.shape[k] as i64;
5391            // The amount is reduced modulo the axis BEFORE the coordinate
5392            // is added to it: a rotate of 9223372036854775806 is a legal
5393            // sentence, and adding it to a coordinate first overflows.
5394            let s = counts.get(k).copied().unwrap_or(0).rem_euclid(len);
5395            idx += (coord[k] as i64 + s).rem_euclid(len) as usize * st[k];
5396        }
5397        push_elem(&mut data, &y.data, idx);
5398        odometer(&mut coord, &y.shape);
5399    }
5400    Ok(Array::new(y.shape.clone(), data))
5401}
5402
5403/// `x ⌽ y` and `x ⊖ y`: rotate one axis of y, by one amount per vector
5404/// along it.
5405///
5406/// APL's left argument is not J's one amount per axis. Exactly one axis
5407/// moves — the last for `⌽`, the leading one for `⊖`, the named one for
5408/// `⌽[k]` — and x holds one amount for each vector along it, so `⍴x` must
5409/// be `⍴y` with that axis removed. A scalar (or a one-item vector, which
5410/// GNU APL accepts as one) rotates every vector by the same amount.
5411/// Anything else is a conformability error: a rank error where the ranks
5412/// disagree and a length error where only the lengths do.
5413fn rotate_apl(x: &Array, y: &Array, last: bool, near: NearInt, span: Span) -> Result<Array> {
5414    let scalar_like = x.rank() == 0 || (x.rank() == 1 && x.count() == 1);
5415    // A scalar has no axis to rotate, so it is its own answer — but only
5416    // for a left argument that could have rotated something.
5417    if y.rank() == 0 {
5418        return if scalar_like {
5419            Ok(y.clone())
5420        } else {
5421            Err(Error::new(
5422                ErrorKind::Rank,
5423                format!(
5424                    "rotate has a rank-{} left argument for a scalar, which needs a scalar",
5425                    x.rank()
5426                ),
5427                Some(span),
5428            ))
5429        };
5430    }
5431    let axis = if last { y.rank() - 1 } else { 0 };
5432    let want: Vec<usize> =
5433        y.shape.iter().enumerate().filter(|&(k, _)| k != axis).map(|(_, &n)| n).collect();
5434    if !scalar_like {
5435        if x.rank() != want.len() {
5436            return Err(Error::new(
5437                ErrorKind::Rank,
5438                format!(
5439                    "rotate has a rank-{} left argument for axis {axis} of {}, which needs rank {}",
5440                    x.rank(),
5441                    show_shape(&y.shape),
5442                    want.len()
5443                ),
5444                Some(span),
5445            ));
5446        }
5447        if x.shape != want {
5448            return Err(Error::new(
5449                ErrorKind::Length,
5450                format!(
5451                    "rotate has a {} left argument for axis {axis} of {}, which needs {}",
5452                    show_shape(&x.shape),
5453                    show_shape(&y.shape),
5454                    show_shape(&want)
5455                ),
5456                Some(span),
5457            ));
5458        }
5459    }
5460    let counts = x
5461        .to_i64_vec_near(near)
5462        .ok_or_else(|| Error::domain("rotate needs integer lengths", span))?;
5463    let len = y.shape[axis] as i64;
5464    let n = y.count();
5465    if n == 0 {
5466        return Ok(y.clone());
5467    }
5468    let st = strides(&y.shape);
5469    let r = y.rank();
5470    let mut data = Data::empty(y.dtype());
5471    let mut coord = vec![0usize; r];
5472    for _ in 0..n {
5473        // Which vector this element sits on, in the order x holds them.
5474        let mut which = 0usize;
5475        for (k, &c) in coord.iter().enumerate() {
5476            if k != axis {
5477                which = which * y.shape[k] + c;
5478            }
5479        }
5480        let s = if scalar_like { counts[0] } else { counts[which] };
5481        // Reduced modulo the axis before the coordinate joins it: the
5482        // amount may be any i64 the program can write.
5483        let s = s.rem_euclid(len);
5484        let mut idx = 0usize;
5485        for (k, &c) in coord.iter().enumerate() {
5486            let c = if k == axis { (c as i64 + s).rem_euclid(len) as usize } else { c };
5487            idx += c * st[k];
5488        }
5489        push_elem(&mut data, &y.data, idx);
5490        odometer(&mut coord, &y.shape);
5491    }
5492    Ok(Array::new(y.shape.clone(), data))
5493}
5494
5495/// A key identifying one element exactly, for equality by hashing. Only
5496/// comparable within one dtype; the two zeros share a key.
5497fn elem_key(d: &Data, i: usize) -> u64 {
5498    match d {
5499        Data::Bool(v) => v[i] as u64,
5500        Data::I64(v) => v[i] as u64,
5501        Data::F64(v) => {
5502            let x = v[i];
5503            if x == 0.0 { 0 } else { x.to_bits() }
5504        }
5505        Data::Complex(v) => cx_key(v[i]),
5506        Data::Char(v) => v[i] as u64,
5507        // A symbol IS its table index, so the index is the key.
5508        Data::Symbol(v) => v[i] as u64,
5509        // Neither a box nor an exact value has a cheap key; their callers
5510        // compare them by content.
5511        Data::Ext(_) | Data::Rat(_) | Data::Box(_) => 0,
5512    }
5513}
5514
5515/// A key comparable across the numeric dtypes: numbers by their float value,
5516/// characters by codepoint. Callers keep the two kinds apart.
5517fn num_key(d: &Data, i: usize) -> u64 {
5518    match d {
5519        Data::Bool(v) => (v[i] as f64).to_bits(),
5520        Data::I64(v) => (v[i] as f64).to_bits(),
5521        Data::F64(v) => {
5522            let x = v[i];
5523            if x == 0.0 { 0.0f64.to_bits() } else { x.to_bits() }
5524        }
5525        Data::Complex(v) => cx_key(v[i]),
5526        Data::Char(v) => v[i] as u64,
5527        Data::Symbol(v) => v[i] as u64,
5528        // As in `elem_key`: never reached for boxed or exact data.
5529        Data::Ext(_) | Data::Rat(_) | Data::Box(_) => 0,
5530    }
5531}
5532
5533/// One key for a complex value; the two parts have to disagree to disagree.
5534fn cx_key(z: Cx) -> u64 {
5535    let bits = |x: f64| if x == 0.0 { 0u64 } else { x.to_bits() };
5536    bits(z[0]) ^ bits(z[1]).rotate_left(32)
5537}
5538
5539/// Distinct items, in the order of their first occurrence.
5540fn nub(y: &Array, tol: Tol) -> Array {
5541    if y.rank() == 0 {
5542        return Array::new(vec![1], y.data.clone());
5543    }
5544    let n = y.items();
5545    let m = y.item_size();
5546    let mut keep = Vec::new();
5547    if y.dtype() == DType::Box || y.dtype().is_exact() {
5548        // Boxed and exact items are compared by content, one against the
5549        // ones kept so far: there is no key to hash.
5550        for i in 0..n {
5551            if !keep.iter().any(|&j| arrays_match(&y.item(i), &y.item(j), tol)) {
5552                keep.push(i);
5553            }
5554        }
5555    } else if y.dtype() == DType::F64 && tol.ct != 0.0 {
5556        // Tolerant equality is not an equivalence a hash can stand in for:
5557        // each float item is compared against the ones already kept.
5558        let mut tv = Vec::new();
5559        let v = borrow_f64(&y.data, &mut tv);
5560        for i in 0..n {
5561            if !keep.iter().any(|&j| (0..m).all(|k| tol.eq(v[i * m + k], v[j * m + k]))) {
5562                keep.push(i);
5563            }
5564        }
5565    } else {
5566        let mut seen: HashSet<Vec<u64>> = HashSet::with_capacity(n);
5567        for i in 0..n {
5568            let key: Vec<u64> = (0..m).map(|k| elem_key(&y.data, i * m + k)).collect();
5569            if seen.insert(key) {
5570                keep.push(i);
5571            }
5572        }
5573    }
5574    let mut data = Data::empty(y.dtype());
5575    for &i in &keep {
5576        for k in 0..m {
5577            push_elem(&mut data, &y.data, i * m + k);
5578        }
5579    }
5580    let mut shape = y.shape.clone();
5581    shape[0] = keep.len();
5582    Array::new(shape, data)
5583}
5584
5585/// Which ordering a grade puts whole arrays in when its items are boxed —
5586/// J's total array ordering, or the APL2 rule GNU APL implements. The two
5587/// disagree at every step, so a comparison says which one it is answering
5588/// for.
5589#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5590enum Tao {
5591    J,
5592    Apl2,
5593    /// Dyalog's total array ordering.
5594    Dyalog,
5595}
5596
5597impl Tao {
5598    fn of(rules: Rules) -> Tao {
5599        match rules.lang {
5600            crate::Lang::J => Tao::J,
5601            crate::Lang::Apl => match rules.nested_grade {
5602                NestedGrade::Apl2 => Tao::Apl2,
5603                NestedGrade::TotalOrder => Tao::Dyalog,
5604            },
5605        }
5606    }
5607
5608    /// The type class compared before the atoms: J puts numeric first,
5609    /// then symbol, then character, then boxed; APL2 puts character first,
5610    /// then numeric, then nested. APL has no symbols of its own, so a
5611    /// symbol that reaches an APL grade sorts with the characters it is
5612    /// made of names of.
5613    fn class(self, dt: DType) -> u8 {
5614        match self {
5615            Tao::J => match dt {
5616                DType::Symbol => 1,
5617                DType::Char => 2,
5618                DType::Box => 3,
5619                _ => 0,
5620            },
5621            Tao::Apl2 => match dt {
5622                DType::Char | DType::Symbol => 0,
5623                DType::Box => 2,
5624                _ => 1,
5625            },
5626            // Dyalog puts every number before every character. A nested
5627            // value is never placed by its own type here: an array with
5628            // atoms is decided by them, and an atomless one by the item it
5629            // would have held (`proto_item`), so the box arm is reached
5630            // only for an empty that has forgotten its prototype.
5631            Tao::Dyalog => match dt {
5632                DType::Char | DType::Symbol => 2,
5633                DType::Box => 1,
5634                _ => 0,
5635            },
5636        }
5637    }
5638}
5639
5640/// A grade's comparator: which total ordering it puts whole arrays in, and
5641/// the tolerance the numbers inside it are read with.
5642///
5643/// APL's `⍋` and `⍒` compare under `⎕CT` — `⍋1.0000000000001 1` is `1 2` in
5644/// GNU APL, the two keys equal and left in the order they came — while J's
5645/// grade is exact whatever the comparison tolerance is: jconsole answers
5646/// `/: 1 1.0000000000001 1` with `0 2 1`.
5647#[derive(Clone, Copy, Debug)]
5648struct Grading {
5649    tao: Tao,
5650    tol: Tol,
5651}
5652
5653impl Grading {
5654    fn of(rules: Rules, tol: Tol) -> Grading {
5655        let tao = Tao::of(rules);
5656        // J's grade is exact, and so is Dyalog's: `⍋2 (1+1E¯14) 1` is
5657        // `3 2 1` there, the two near-equal keys separated rather than
5658        // tied. Only the APL2 line reads `⎕CT` here.
5659        let exact = tao == Tao::J || tao == Tao::Dyalog;
5660        Grading { tao, tol: if exact { Tol { ct: 0.0, ..tol } } else { tol } }
5661    }
5662
5663    fn class(self, dt: DType) -> u8 {
5664        self.tao.class(dt)
5665    }
5666}
5667
5668/// Order two whole arrays, which is how a grade compares boxed items.
5669///
5670/// J compares the type class first — and an EMPTY array has no atoms to
5671/// take a class from, so it takes the lowest one whatever its type, which
5672/// is why `/: (<''),(<<1)` puts the empty character list first and two
5673/// empties of different types tie. Then the rank, then the shape read with
5674/// the LAST axis most significant, then the atoms in row-major order.
5675///
5676/// APL2 compares the rank first, then the shape read from the FIRST axis,
5677/// then the atoms, where a character precedes a number precedes a nested
5678/// value; two arrays with no atoms are separated by their types instead.
5679///
5680/// Both are exact — a grade never reads the comparison tolerance — and a
5681/// NaN ties with everything, which keeps the sort total.
5682fn cmp_items_total(x: &Array, y: &Array, ord: Grading) -> std::cmp::Ordering {
5683    use std::cmp::Ordering::Equal;
5684    match ord.tao {
5685        Tao::Dyalog => cmp_items_dyalog(x, y, ord),
5686        Tao::J => {
5687            let class = |a: &Array| if a.count() == 0 { 0 } else { ord.class(a.dtype()) };
5688            class(x)
5689                .cmp(&class(y))
5690                .then_with(|| x.rank().cmp(&y.rank()))
5691                .then_with(|| x.shape.iter().rev().cmp(y.shape.iter().rev()))
5692                .then_with(|| cmp_atoms(x, y, ord))
5693        }
5694        Tao::Apl2 => x
5695            .rank()
5696            .cmp(&y.rank())
5697            .then_with(|| x.shape.iter().cmp(y.shape.iter()))
5698            .then_with(|| cmp_atoms(x, y, ord))
5699            .then_with(|| {
5700                if x.count() == 0 {
5701                    ord.class(x.dtype()).cmp(&ord.class(y.dtype()))
5702                } else {
5703                    Equal
5704                }
5705            }),
5706    }
5707}
5708
5709/// Two whole arrays in Dyalog's total array ordering.
5710///
5711/// The shapes are brought together rather than compared: the lower rank
5712/// gains leading 1s, and each axis is taken to the longer of the two, so
5713/// the arrays are read position by position over the shape that covers
5714/// both. A position one array has and the other does not answers at once —
5715/// what is not there sorts below every value there is — and a position
5716/// both hold compares its atoms, which recurses where an atom is nested.
5717/// Only arrays with no atoms to separate them reach the type (numbers,
5718/// then nested values, then characters) and then the shape, which is read
5719/// with the LAST axis most significant.
5720///
5721/// Derived from the recorded Dyalog answers in
5722/// `crates/libjay/tests/snapshots/apl/grade.snap`, which is what pins it.
5723fn cmp_items_dyalog(x: &Array, y: &Array, ord: Grading) -> std::cmp::Ordering {
5724    use std::cmp::Ordering::{Equal, Greater, Less};
5725    // Two simple scalars are the bottom of the recursion; everything else
5726    // is read as an array of atoms.
5727    if x.rank() == 0 && y.rank() == 0 && x.dtype() != DType::Box && y.dtype() != DType::Box {
5728        return cmp_atoms(x, y, ord);
5729    }
5730    let rank = x.rank().max(y.rank());
5731    let extend = |a: &Array| -> Vec<usize> {
5732        let mut s = vec![1usize; rank - a.rank()];
5733        s.extend_from_slice(&a.shape);
5734        s
5735    };
5736    let (sx, sy) = (extend(x), extend(y));
5737    let common: Vec<usize> = (0..rank).map(|k| sx[k].max(sy[k])).collect();
5738    let (xr, yr) = (x.to_row_major(), y.to_row_major());
5739    let (dx, dy) = (xr.row_major_data(), yr.row_major_data());
5740    let (stx, sty) = (strides(&sx), strides(&sy));
5741    let mut order = Equal;
5742    if !common.contains(&0) {
5743        let mut coord = vec![0usize; rank];
5744        loop {
5745            let inside = |s: &[usize]| (0..rank).all(|k| coord[k] < s[k]);
5746            let at = |st: &[usize]| -> usize { (0..rank).map(|k| coord[k] * st[k]).sum() };
5747            let here = match (inside(&sx), inside(&sy)) {
5748                (true, true) => {
5749                    cmp_items_dyalog(&atom_array(dx, at(&stx)), &atom_array(dy, at(&sty)), ord)
5750                }
5751                // What is not there is below what is.
5752                (true, false) => Greater,
5753                (false, true) => Less,
5754                (false, false) => Equal,
5755            };
5756            if here != Equal {
5757                order = here;
5758                break;
5759            }
5760            // The odometer wraps to all zeros when the last position is
5761            // done, and every position either decides or holds equal
5762            // atoms, so this walks no further than the shorter array.
5763            odometer(&mut coord, &common);
5764            if coord.iter().all(|&c| c == 0) {
5765                break;
5766            }
5767        }
5768    }
5769    if order != Equal {
5770        return order;
5771    }
5772    // Nothing was there to compare, so the arrays are separated by the item
5773    // they WOULD have held and then by their shape, last axis first.
5774    match (proto_item(x), proto_item(y)) {
5775        // The prototypes are values like any other, and are compared under
5776        // the same tolerance the atoms would have been.
5777        (Some(px), Some(py)) => cmp_items_dyalog(&px, &py, ord),
5778        _ => ord.class(x.dtype()).cmp(&ord.class(y.dtype())),
5779    }
5780    .then_with(|| x.shape.iter().rev().cmp(y.shape.iter().rev()))
5781}
5782
5783/// The item an atomless array would have held, as an array of its own: a
5784/// nested empty's remembered prototype, and for a simple one the fill its
5785/// type implies — a zero, or a blank. `None` where there is nothing to say,
5786/// which is a nested empty that has forgotten (and an array with atoms,
5787/// which is never separated this way).
5788fn proto_item(a: &Array) -> Option<Array> {
5789    if let Some(p) = a.proto() {
5790        return Some(p.clone());
5791    }
5792    match a.dtype() {
5793        DType::Box => None,
5794        dt => Some(Array::new(vec![], fill_data(dt, 1))),
5795    }
5796}
5797
5798/// Element `i` of a buffer as an array of its own: a box gives up its
5799/// contents, anything else is a simple scalar.
5800fn atom_array(d: &Data, i: usize) -> Array {
5801    match d {
5802        Data::Box(v) => v[i].clone(),
5803        _ => {
5804            let mut one = Data::empty(d.dtype());
5805            push_elem(&mut one, d, i);
5806            Array::new(vec![], one)
5807        }
5808    }
5809}
5810
5811/// The atoms of two arrays of the same shape, in row-major order. A boxed
5812/// atom is compared by its contents, which is where the ordering recurses.
5813fn cmp_atoms(x: &Array, y: &Array, ord: Grading) -> std::cmp::Ordering {
5814    use std::cmp::Ordering::Equal;
5815    let n = x.count();
5816    if n == 0 {
5817        return Equal;
5818    }
5819    let (xr, yr) = (x.to_row_major(), y.to_row_major());
5820    let (dx, dy) = (xr.row_major_data(), yr.row_major_data());
5821    if matches!(dx, Data::Box(_)) || matches!(dy, Data::Box(_)) {
5822        return (0..n)
5823            .map(|i| cmp_items_total(&atom_array(dx, i), &atom_array(dy, i), ord))
5824            .find(|o| *o != Equal)
5825            .unwrap_or(Equal);
5826    }
5827    // Neither side is boxed, so one class covers all of each side's atoms.
5828    let classes = ord.class(dx.dtype()).cmp(&ord.class(dy.dtype()));
5829    if classes != Equal {
5830        return classes;
5831    }
5832    match (dx, dy) {
5833        (Data::Char(a), Data::Char(b)) => a[..n].cmp(&b[..n]),
5834        _ => cmp_numbers(dx, dy, n, ord.tol),
5835    }
5836}
5837
5838/// Two numeric buffers, `n` elements each, compared in order. The widening
5839/// is the one `arrays_match` uses, so `1r2` and `0.5` compare where they
5840/// belong however each is spelled.
5841fn cmp_numbers(dx: &Data, dy: &Data, n: usize, tol: Tol) -> std::cmp::Ordering {
5842    use std::cmp::Ordering::Equal;
5843    let seek = |f: &dyn Fn(usize) -> std::cmp::Ordering| {
5844        (0..n).map(f).find(|o| *o != Equal).unwrap_or(Equal)
5845    };
5846    match DType::promote(dx.dtype(), dy.dtype()) {
5847        Some(DType::Complex) => {
5848            let (mut ta, mut tb) = (Vec::new(), Vec::new());
5849            let (a, b) = (borrow_cx(dx, &mut ta), borrow_cx(dy, &mut tb));
5850            seek(&|k| tol_ord(a[k][0], b[k][0], tol).then_with(|| tol_ord(a[k][1], b[k][1], tol)))
5851        }
5852        Some(DType::F64) => {
5853            let (mut ta, mut tb) = (Vec::new(), Vec::new());
5854            let (a, b) = (borrow_f64(dx, &mut ta), borrow_f64(dy, &mut tb));
5855            seek(&|k| tol_ord(a[k], b[k], tol))
5856        }
5857        Some(t) if t.is_exact() => match (to_rat_vec(dx), to_rat_vec(dy)) {
5858            (Some(a), Some(b)) => seek(&|k| a[k].cmp(&b[k])),
5859            _ => Equal,
5860        },
5861        // Characters and boxes never reach here: the classes agreed.
5862        None => Equal,
5863        Some(_) => {
5864            let (mut ta, mut tb) = (Vec::new(), Vec::new());
5865            let (a, b) = (borrow_i64(dx, &mut ta), borrow_i64(dy, &mut tb));
5866            seek(&|k| a[k].cmp(&b[k]))
5867        }
5868    }
5869}
5870
5871/// Compare items `i` and `j` (of `m` elements each) elementwise, left to
5872/// right. Characters order by codepoint; a NaN compares equal to anything,
5873/// which keeps the sort total.
5874fn cmp_items(d: &Data, i: usize, j: usize, m: usize, ord: Grading) -> std::cmp::Ordering {
5875    use std::cmp::Ordering::Equal;
5876    let (a, b) = (i * m, j * m);
5877    let ord = |k: usize| match d {
5878        Data::Bool(v) => v[a + k].cmp(&v[b + k]),
5879        Data::I64(v) => v[a + k].cmp(&v[b + k]),
5880        Data::F64(v) => tol_ord(v[a + k], v[b + k], ord.tol),
5881        // Grading a complex array orders it by real part then imaginary,
5882        // which is the order J's `/:` puts it in and the dialect's
5883        // `ComplexOrder::RealThenImaginary`; `check_gradable` has already
5884        // refused the other reading. The ordering VERBS still refuse
5885        // complex outright: a grade is a permutation, not a claim about
5886        // size.
5887        Data::Complex(v) => tol_ord(v[a + k][0], v[b + k][0], ord.tol)
5888            .then_with(|| tol_ord(v[a + k][1], v[b + k][1], ord.tol)),
5889        Data::Char(v) => v[a + k].cmp(&v[b + k]),
5890        // Symbols order by the NAME behind the index, not by the order
5891        // the two names happened to be interned in.
5892        Data::Symbol(v) => crate::symbol::cmp(v[a + k], v[b + k]),
5893        // The exact types order by value, however they are spelled: `2r4`
5894        // grades exactly where `1r2` does.
5895        Data::Ext(v) => v[a + k].cmp(&v[b + k]),
5896        Data::Rat(v) => v[a + k].cmp(&v[b + k]),
5897        // A boxed element is a whole array: the ordering of the language
5898        // being graded in decides between two of them.
5899        Data::Box(v) => cmp_items_total(&v[a + k], &v[b + k], ord),
5900    };
5901    (0..m).map(ord).find(|o| *o != Equal).unwrap_or(Equal)
5902}
5903
5904/// The stable permutation that sorts the items of `y`.
5905fn grade_order(y: &Array, down: bool, ord: Grading) -> Vec<usize> {
5906    if y.rank() == 0 {
5907        return vec![0];
5908    }
5909    let n = y.items();
5910    let m = y.item_size();
5911    let mut idx: Vec<usize> = (0..n).collect();
5912    // A stable sort leaves equal items in their original order, which is
5913    // what both languages promise, ascending and descending alike.
5914    if down {
5915        idx.sort_by(|&a, &b| cmp_items(&y.data, b, a, m, ord));
5916    } else {
5917        idx.sort_by(|&a, &b| cmp_items(&y.data, a, b, m, ord));
5918    }
5919    idx
5920}
5921
5922/// `x ⍋ y` and `x ⍒ y`: every character of y is keyed by where it first
5923/// occurs in the collating array x — the coordinate read with the LAST axis
5924/// most significant, and one past the end for a character x does not hold —
5925/// and the items of y are ordered by those keys read left to right.
5926fn collate_grade(x: &Array, y: &Array, down: bool, origin: i64, span: Span) -> Result<Array> {
5927    let chars_of = |a: &Array| -> Result<Vec<char>> {
5928        match a.row_major_data() {
5929            Data::Char(v) => Ok(v.as_slice().to_vec()),
5930            _ => Err(Error::domain("a collating grade takes characters", span)),
5931        }
5932    };
5933    let (xs, ys) = (chars_of(x)?, chars_of(y)?);
5934    let xshape = if x.rank() == 0 { vec![1] } else { x.shape.clone() };
5935    let width = xshape.len();
5936    // The key of a character: its first coordinate in x, reversed so the
5937    // last axis decides first. A character x does not hold sorts after
5938    // every one it does.
5939    let absent: Vec<usize> = xshape.iter().rev().copied().collect();
5940    let mut keys: std::collections::HashMap<char, Vec<usize>> =
5941        std::collections::HashMap::new();
5942    let xst = strides(&xshape);
5943    for (i, &c) in xs.iter().enumerate() {
5944        keys.entry(c).or_insert_with(|| {
5945            (0..width).map(|a| (i / xst[a]) % xshape[a]).rev().collect()
5946        });
5947    }
5948    let key_of = |c: char| keys.get(&c).unwrap_or(&absent).clone();
5949    let n = if y.rank() == 0 { 1 } else { y.items() };
5950    let m = if n == 0 { 0 } else { ys.len() / n };
5951    let item_keys: Vec<Vec<usize>> = (0..n)
5952        .map(|i| ys[i * m..(i + 1) * m].iter().flat_map(|&c| key_of(c)).collect())
5953        .collect();
5954    let mut idx: Vec<usize> = (0..n).collect();
5955    if down {
5956        idx.sort_by(|&a, &b| item_keys[b].cmp(&item_keys[a]));
5957    } else {
5958        idx.sort_by(|&a, &b| item_keys[a].cmp(&item_keys[b]));
5959    }
5960    Ok(Array::from_i64(idx.into_iter().map(|i| origin + i as i64).collect()))
5961}
5962
5963/// `5!:1 <'name'`: the atomic representation of what the name stands for.
5964/// A verb answers with the representation of the verb, a value with the
5965/// noun pair; either way the answer is boxed, as the reference has it.
5966fn atomic_rep(y: &Array, ctx: &Ctx<'_>, span: Span) -> Result<Array> {
5967    let name = match y.as_boxes() {
5968        Some([b]) if y.rank() == 0 => crate::gerund::text_of(b),
5969        _ => None,
5970    };
5971    let Some(name) = name else {
5972        return Err(Error::domain("5!:1 takes a boxed name", span));
5973    };
5974    if let Some(v) = ctx.env.verb(&name) {
5975        let ar = crate::gerund::verb_ar(v).ok_or_else(|| {
5976            Error::not_yet(
5977                format!("the atomic representation of {}", v.name()),
5978                span,
5979            )
5980        })?;
5981        return Ok(Array::boxed(ar.to_array()));
5982    }
5983    match ctx.env.get(&name) {
5984        Some(a) => Ok(Array::boxed(crate::gerund::Ar::Noun(a).to_array())),
5985        None => Err(Error::new(
5986            ErrorKind::Value,
5987            format!("undefined name: {name}"),
5988            Some(span),
5989        )),
5990    }
5991}
5992
5993/// `{ y`: the catalogue — every way of taking one element from each item
5994/// of y. The shapes of the items, opened, make the result's shape, and each
5995/// element of it is the boxed vector of one choice from each.
5996fn catalogue(y: &Array, span: Span) -> Result<Array> {
5997    let items = if y.rank() == 0 { vec![y.clone()] } else { y.cells(1) };
5998    // A boxed item stands for its contents; a simple one for itself.
5999    let opened: Vec<Array> = items
6000        .iter()
6001        .map(|it| match it.as_boxes() {
6002            Some(bs) if it.rank() == 0 => bs[0].clone(),
6003            _ => it.clone(),
6004        })
6005        .collect();
6006    let mut shape: Vec<usize> = Vec::new();
6007    for o in &opened {
6008        shape.extend_from_slice(&o.shape);
6009    }
6010    let total: usize = shape.iter().product();
6011    let mut out = Vec::with_capacity(total);
6012    let mut coord = vec![0usize; shape.len()];
6013    for _ in 0..total {
6014        let mut at = 0usize;
6015        let mut picks = Vec::with_capacity(opened.len());
6016        for o in &opened {
6017            let st = strides(&o.shape);
6018            let idx: usize = (0..o.rank()).map(|a| coord[at + a] * st[a]).sum();
6019            at += o.rank();
6020            let mut data = Data::empty(o.dtype());
6021            push_elem(&mut data, o.row_major_data(), idx);
6022            picks.push(Array::new(vec![], data));
6023        }
6024        out.push(assemble(&[picks.len()], picks, span)?);
6025        odometer(&mut coord, &shape);
6026    }
6027    Ok(Array::new(shape, Data::Box(out.into())))
6028}
6029
6030/// `e. y`: for every element of y, which items of the raze of y it holds —
6031/// so the answer is shaped `($y), #items of the raze`.
6032fn raze_in(y: &Array, tol: Tol, span: Span) -> Result<Array> {
6033    let all = raze(y, span)?;
6034    let n = if all.rank() == 0 { 1 } else { all.items() };
6035    let elements: Vec<Array> = (0..y.count())
6036        .map(|i| {
6037            let mut data = Data::empty(y.dtype());
6038            push_elem(&mut data, y.row_major_data(), i);
6039            let one = Array::new(vec![], data);
6040            match one.as_boxes() {
6041                Some(bs) => bs[0].clone(),
6042                None => one,
6043            }
6044        })
6045        .collect();
6046    let mut out = Vec::with_capacity(elements.len() * n);
6047    for e in &elements {
6048        let row = member_j(&all, e, tol);
6049        out.extend_from_slice(row.to_i64_vec().unwrap_or_default().as_slice());
6050    }
6051    let mut shape = y.shape.clone();
6052    shape.push(n);
6053    Ok(Array::new(shape, Data::Bool(out.into_iter().map(|v| v as u8).collect::<Vec<u8>>().into())))
6054}
6055
6056/// Select items of `y` in the given order.
6057fn select_items(y: &Array, order: &[usize]) -> Array {
6058    let m = y.item_size();
6059    let mut data = Data::empty(y.dtype());
6060    for &i in order {
6061        for k in 0..m {
6062            push_elem(&mut data, &y.data, i * m + k);
6063        }
6064    }
6065    let mut shape = y.shape.clone();
6066    shape[0] = order.len();
6067    Array::new(shape, data)
6068}
6069
6070/// What a grade refuses, and the dialect setting it reads.
6071///
6072/// A grade has to be total over complex values, and the dialect says in
6073/// which order; only one of the two readings is implemented.
6074fn check_gradable(y: &Array, rules: Rules, span: Span) -> Result<()> {
6075    if y.dtype() == DType::Complex && rules.complex_order != ComplexOrder::RealThenImaginary {
6076        return Err(Error::not_yet("grading complex values by magnitude and angle", span));
6077    }
6078    Ok(())
6079}
6080
6081/// `x /: y` is `(/: y) { x`: the grade of y is an index into x, so the two
6082/// lengths need not agree — a shorter key selects fewer items, and only an
6083/// index past the end of x is an error.
6084fn grade_select(
6085    x: &Array,
6086    y: &Array,
6087    down: bool,
6088    rules: Rules,
6089    tol: Tol,
6090    span: Span,
6091) -> Result<Array> {
6092    check_gradable(y, rules, span)?;
6093    let order = grade_order(y, down, Grading::of(rules, tol));
6094    // An atom is ONE item, so the only index it answers is the first: J
6095    // reads `5 /: 1` as 5 and refuses `5 /: 1 2 3`, where a lenient reading
6096    // would hand the atom back for any key at all.
6097    if x.rank() == 0 {
6098        if let Some(&past) = order.iter().find(|&&i| i > 0) {
6099            return Err(Error::domain(
6100                format!("index {past} is out of range: the argument has 1 item"),
6101                span,
6102            ));
6103        }
6104        // Selecting that one item as many times as the grade asks: no key
6105        // at all answers the empty, which is what `0.5 /: i.0` is.
6106        return Ok(select_items(&as_list(x), &order));
6107    }
6108    if let Some(&past) = order.iter().find(|&&i| i >= x.items()) {
6109        return Err(Error::domain(
6110            format!("index {past} is out of range: the argument has {} items", x.items()),
6111            span,
6112        ));
6113    }
6114    Ok(select_items(x, &order))
6115}
6116
6117/// Whole-array equality: same shape and same values. Characters never equal
6118/// numbers; `1` equals `1.0`; NaN equals nothing.
6119pub(crate) fn arrays_match(x: &Array, y: &Array, tol: Tol) -> bool {
6120    if x.shape != y.shape {
6121        return false;
6122    }
6123    // The comparison is element against element in buffer order, so two
6124    // values laid out differently are compared in the one order.
6125    if x.layout() != y.layout() {
6126        return arrays_match(&x.to_row_major(), &y.to_row_major(), tol);
6127    }
6128    // Two empty arrays of the same shape match whatever their types are,
6129    // which is what both references answer for `'' -: i. 0`.
6130    if x.count() == 0 {
6131        return true;
6132    }
6133    if let (Data::Box(a), Data::Box(b)) = (&x.data, &y.data) {
6134        return a.iter().zip(b.iter()).all(|(p, q)| arrays_match(p, q, tol));
6135    }
6136    let (dx, dy) = (x.dtype(), y.dtype());
6137    match DType::promote(dx, dy) {
6138        None => false,
6139        Some(DType::Char) => match (&x.data, &y.data) {
6140            (Data::Char(a), Data::Char(b)) => a.as_slice() == b.as_slice(),
6141            _ => false,
6142        },
6143        // Two symbols are the same symbol exactly when they carry the same
6144        // table index, which is the whole point of interning them.
6145        Some(DType::Symbol) => match (&x.data, &y.data) {
6146            (Data::Symbol(a), Data::Symbol(b)) => a.as_slice() == b.as_slice(),
6147            _ => false,
6148        },
6149        Some(DType::F64) => {
6150            let (mut ta, mut tb) = (Vec::new(), Vec::new());
6151            let a = borrow_f64(&x.data, &mut ta);
6152            let b = borrow_f64(&y.data, &mut tb);
6153            a.iter().zip(b).all(|(p, q)| tol.eq(*p, *q))
6154        }
6155        Some(DType::Complex) => {
6156            let (mut ta, mut tb) = (Vec::new(), Vec::new());
6157            let a = borrow_cx(&x.data, &mut ta);
6158            let b = borrow_cx(&y.data, &mut tb);
6159            a.iter().zip(b).all(|(p, q)| tol.eq_cx(*p, *q))
6160        }
6161        Some(t) if t.is_exact() => match (to_rat_vec(&x.data), to_rat_vec(&y.data)) {
6162            (Some(a), Some(b)) => a == b,
6163            _ => false,
6164        },
6165        Some(_) => {
6166            let (mut ta, mut tb) = (Vec::new(), Vec::new());
6167            let a = borrow_i64(&x.data, &mut ta);
6168            let b = borrow_i64(&y.data, &mut tb);
6169            a.iter().zip(b).all(|(p, q)| p == q)
6170        }
6171    }
6172}
6173
6174/// Item `i` of `a`, treating a scalar as an array of one item.
6175fn item_or_self(a: &Array, i: usize) -> Array {
6176    if a.rank() == 0 { a.clone() } else { a.item(i) }
6177}
6178
6179/// `x e. y`: for every cell of x shaped like an item of y, is it an item
6180/// of y? A cell of the wrong shape simply is not one, as in J.
6181fn member_j(x: &Array, y: &Array, tol: Tol) -> Array {
6182    let cell_rank = y.rank().saturating_sub(1).min(x.rank());
6183    let frame_rank = x.rank() - cell_rank;
6184    let frame: Vec<usize> = x.shape[..frame_rank].to_vec();
6185    let nf: usize = frame.iter().product();
6186    let items = y.items();
6187    let mut out = Vec::with_capacity(nf);
6188    for i in 0..nf {
6189        let cell = x.cell_at(frame_rank, i);
6190        out.push((0..items).any(|j| arrays_match(&cell, &item_or_self(y, j), tol)) as u8);
6191    }
6192    Array::new(frame, Data::Bool(out.into()))
6193}
6194
6195/// `x ∊ y`: for every element of x, does that value occur anywhere in y?
6196fn member_apl(x: &Array, y: &Array, tol: Tol) -> Array {
6197    let n = x.count();
6198    if x.dtype() == DType::Box
6199        || y.dtype() == DType::Box
6200        || x.dtype().is_exact()
6201        || y.dtype().is_exact()
6202    {
6203        // A box's elements are whole arrays and an exact value has no cheap
6204        // key, so both are compared by content; a box never equals a plain
6205        // number or character.
6206        // `⊂5` is `5` in APL, so a box holding a simple scalar compares as
6207        // that scalar: `1 2 3 ∊ (1 2)(3)` finds the 3.
6208        let opened = |a: &Array, i: usize| -> Array {
6209            let e = atom(a, i);
6210            match e.as_boxes() {
6211                Some([b]) if b.rank() == 0 && b.dtype() != DType::Box => b.clone(),
6212                _ => e,
6213            }
6214        };
6215        let out: Vec<u8> = (0..n)
6216            .map(|i| {
6217                let e = opened(x, i);
6218                u8::from((0..y.count()).any(|j| arrays_match(&e, &opened(y, j), tol)))
6219            })
6220            .collect();
6221        return Array::new(x.shape.clone(), Data::Bool(out.into()));
6222    }
6223    if x.dtype() != y.dtype()
6224        && [x.dtype(), y.dtype()].iter().any(|&d| matches!(d, DType::Char | DType::Symbol))
6225    {
6226        return Array::new(x.shape.clone(), Data::Bool(vec![0u8; n].into()));
6227    }
6228    if tol.ct != 0.0
6229        && (x.dtype() == DType::F64 || y.dtype() == DType::F64)
6230        && x.dtype() != DType::Char
6231    {
6232        // Tolerance rules a hash out; the values are compared directly.
6233        let (mut tx, mut ty) = (Vec::new(), Vec::new());
6234        let xs = borrow_f64(&x.data, &mut tx);
6235        let ys = borrow_f64(&y.data, &mut ty);
6236        let out: Vec<u8> =
6237            xs.iter().map(|a| ys.iter().any(|b| tol.eq(*a, *b)) as u8).collect();
6238        return Array::new(x.shape.clone(), Data::Bool(out.into()));
6239    }
6240    let seen: HashSet<u64> = (0..y.count()).map(|i| num_key(&y.data, i)).collect();
6241    let out: Vec<u8> =
6242        (0..n).map(|i| seen.contains(&num_key(&x.data, i)) as u8).collect();
6243    Array::new(x.shape.clone(), Data::Bool(out.into()))
6244}
6245
6246/// `x i. y` / `x ⍳ y`: where each cell of y sits among the items of x.
6247///
6248/// `vector_left` is the Dyalog reading, where the lookup table is a vector
6249/// and nothing else; without it the items of a left argument of any rank
6250/// are searched, which is what J and the APL2 line do.
6251fn index_of(
6252    x: &Array,
6253    y: &Array,
6254    origin: i64,
6255    vector_left: bool,
6256    tol: Tol,
6257    span: Span,
6258) -> Result<Array> {
6259    if vector_left && x.rank() != 1 {
6260        return Err(Error::new(
6261            ErrorKind::Rank,
6262            format!("⍳ looks up in a vector, and its left argument has rank {}", x.rank()),
6263            Some(span),
6264        ));
6265    }
6266    let cell_rank = x.rank().saturating_sub(1).min(y.rank());
6267    let frame_rank = y.rank() - cell_rank;
6268    let frame: Vec<usize> = y.shape[..frame_rank].to_vec();
6269    let nf: usize = frame.iter().product();
6270    let items = x.items();
6271    let mut out = Vec::with_capacity(nf);
6272    for i in 0..nf {
6273        let cell = y.cell_at(frame_rank, i);
6274        let at = (0..items)
6275            .find(|&j| arrays_match(&cell, &item_or_self(x, j), tol))
6276            .unwrap_or(items);
6277        out.push(origin + at as i64);
6278    }
6279    Ok(Array::new(frame, Data::I64(out.into())))
6280}
6281
6282/// `x { y` for one index atom: the rank machinery supplies the framing.
6283fn from_index(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
6284    // A boxed index is J's index specification, which reaches several axes
6285    // at once; a plain one selects an item.
6286    if let Some(spec) = x.as_boxes().and_then(<[Array]>::first) {
6287        let spec = index_spec(spec, y, near, span)?;
6288        return Ok(select_spec(&spec, y));
6289    }
6290    let idx = x
6291        .to_i64_vec_near(near)
6292        .ok_or_else(|| Error::domain("index must be an integer", span))?;
6293    let Some(&i) = idx.first() else {
6294        return Err(Error::internal("from_index with no index"));
6295    };
6296    let n = y.items() as i64;
6297    let k = if i < 0 { i + n } else { i };
6298    if k < 0 || k >= n {
6299        return Err(Error::domain(
6300            format!("index {i} is out of range: the argument has {n} items"),
6301            span,
6302        ));
6303    }
6304    Ok(item_or_self(y, k as usize))
6305}
6306
6307/// Bring `a` up to `rank` axes for catenation along `axis`. A scalar spreads
6308/// over one cross section of the other argument; one missing axis becomes a
6309/// length-1 axis at `axis`.
6310fn cat_promote(
6311    a: &Array,
6312    other: &Array,
6313    rank: usize,
6314    axis: usize,
6315    deep: bool,
6316    span: Span,
6317) -> Result<Array> {
6318    if a.rank() == rank {
6319        return Ok(a.clone());
6320    }
6321    if a.rank() == 0 {
6322        let mut shape =
6323            if other.rank() == rank { other.shape.clone() } else { vec![1usize; rank] };
6324        shape[axis] = 1;
6325        let n: usize = shape.iter().product();
6326        let mut data = Data::empty(a.dtype());
6327        for _ in 0..n {
6328            push_elem(&mut data, &a.data, 0);
6329        }
6330        return Ok(Array::new(shape, data));
6331    }
6332    // One axis short, the value is one item of the answer. J's `,` goes on
6333    // taking a wider gap the same way — `1 2 3 , (2 1 3$1)` is a rank-3
6334    // answer whose first item is the vector, filled out to the item shape —
6335    // while APL holds the two ranks to within one of each other.
6336    if a.rank() + 1 == rank || (deep && a.rank() < rank) {
6337        let mut shape = a.shape.clone();
6338        for _ in a.rank()..rank {
6339            shape.insert(axis, 1);
6340        }
6341        return Ok(Array::new(shape, a.data.clone()));
6342    }
6343    Err(Error::new(
6344        ErrorKind::Rank,
6345        format!("cannot catenate rank {} with rank {}", a.rank(), other.rank()),
6346        Some(span),
6347    ))
6348}
6349
6350/// The type two arrays that share none take when at least one of them holds
6351/// no elements.
6352///
6353/// J lets an empty operand join anything: `(0$'a') , 1 2 3` is `1 2 3`, and
6354/// an empty box vanishes beside characters the same way, because no element
6355/// of the empty side ever becomes an element of the result. Where both
6356/// sides are empty the wider container wins — a box over a character, a
6357/// character over a number.
6358fn empty_type(x: &Array, y: &Array) -> Option<DType> {
6359    match (x.count() == 0, y.count() == 0) {
6360        (true, false) => Some(y.dtype()),
6361        (false, true) => Some(x.dtype()),
6362        (true, true) => Some(match (x.dtype(), y.dtype()) {
6363            (DType::Box, _) | (_, DType::Box) => DType::Box,
6364            (DType::Char, _) | (_, DType::Char) => DType::Char,
6365            (a, b) => DType::promote(a, b)?,
6366        }),
6367        (false, false) => None,
6368    }
6369}
6370
6371/// Catenate along the leading or the last axis.
6372pub(crate) fn catenate(
6373    x: &Array,
6374    y: &Array,
6375    leading: bool,
6376    fill: bool,
6377    span: Span,
6378) -> Result<Array> {
6379    let rank = x.rank().max(y.rank()).max(1);
6380    let axis = if leading { 0 } else { rank - 1 };
6381    let deep = fill && leading;
6382    let xa = cat_promote(x, y, rank, axis, deep, span)?;
6383    let ya = cat_promote(y, x, rank, axis, deep, span)?;
6384    // J lets an operand with no elements join anything, taking the other
6385    // side's type instead of clashing with it: `(0$'a') , 1 2 3` is
6386    // `1 2 3`. The retyping happens here, before any fill is worked out, so
6387    // that the fill an unequal axis needs is the RESULT's — the empty
6388    // planes of `(2 0 3$0) , 'hello'` come out as spaces, not as zeros.
6389    let (xa, ya) = match empty_type(&xa, &ya)
6390        .filter(|_| fill && DType::promote(xa.dtype(), ya.dtype()).is_none())
6391    {
6392        None => (xa, ya),
6393        Some(dt) => {
6394            let retype = |a: Array| {
6395                if a.count() == 0 && a.dtype() != dt {
6396                    Array::new(a.shape.clone(), Data::empty(dt))
6397                } else {
6398                    a
6399                }
6400            };
6401            (retype(xa), retype(ya))
6402        }
6403    };
6404    // Axes other than the one being joined must agree. J overtakes both
6405    // sides to the larger length, which fills; APL insists they conform,
6406    // and the reference refuses the ragged case outright.
6407    let mut ragged = false;
6408    let want: Vec<i64> = (0..rank)
6409        .map(|k| {
6410            ragged |= k != axis && xa.shape[k] != ya.shape[k];
6411            xa.shape[k].max(ya.shape[k]) as i64
6412        })
6413        .collect();
6414    if ragged && !fill {
6415        return Err(Error::new(
6416            ErrorKind::Length,
6417            format!(
6418                "cannot catenate: left shape {}, right shape {}",
6419                show_shape(&xa.shape),
6420                show_shape(&ya.shape)
6421            ),
6422            Some(span),
6423        ));
6424    }
6425    let (xa, ya) = if ragged {
6426        let fit = |a: &Array| -> Result<Array> {
6427            let mut to = want.clone();
6428            to[axis] = a.shape[axis] as i64;
6429            // The lengths are ours, not the program's: no float
6430            // reaches the near-integer admission on this path.
6431            take(&Array::from_i64(to), a, false, false, NearInt::J, span)
6432        };
6433        (fit(&xa)?, fit(&ya)?)
6434    } else {
6435        (xa, ya)
6436    };
6437    // APL2 catenates a nested array to a simple one by enclosing the
6438    // simple side's items: `(1 2),⊂3 4` is a three-item nested vector. J
6439    // refuses the mixture, and its `fill` rule is what tells them apart.
6440    let (xa, ya) = if !fill && (xa.dtype() == DType::Box) != (ya.dtype() == DType::Box) {
6441        (nest_like(&xa, &ya), nest_like(&ya, &xa))
6442    } else {
6443        (xa, ya)
6444    };
6445    // And where two SIMPLE arrays share no type, APL builds a mixed simple
6446    // one rather than refusing: `1 2,'ab'` is a four-element vector of two
6447    // numbers and two characters, depth 1. J has no such value.
6448    let mixing = !fill
6449        && xa.dtype() != DType::Box
6450        && ya.dtype() != DType::Box
6451        && DType::promote(xa.dtype(), ya.dtype()).is_none();
6452    let (xa, ya) =
6453        if mixing { (spread_scalars(&xa), spread_scalars(&ya)) } else { (xa, ya) };
6454    let dt = DType::promote(xa.dtype(), ya.dtype())
6455        .ok_or_else(|| {
6456            let boxed = xa.dtype() == DType::Box || ya.dtype() == DType::Box;
6457            let what = if boxed {
6458                "cannot catenate boxed and unboxed data; box the other side first"
6459            } else {
6460                "cannot catenate character and numeric data"
6461            };
6462            Error::new(ErrorKind::Type, what, Some(span))
6463        })?;
6464    let widen = |a: &Array| -> Result<Data> {
6465        if a.dtype() == dt {
6466            Ok(a.data.clone())
6467        } else if a.count() == 0 {
6468            // An empty side brings no element to convert, so it takes the
6469            // result's type outright — there is no character to read as a
6470            // number, which is the conversion that has no meaning.
6471            Ok(Data::empty(dt))
6472        } else {
6473            a.data.cast(dt).ok_or_else(|| Error::internal("unsupported widening in catenate"))
6474        }
6475    };
6476    let xd = widen(&xa)?;
6477    let yd = widen(&ya)?;
6478    let outer: usize = xa.shape[..axis].iter().product();
6479    let ix: usize = xa.shape[axis..].iter().product();
6480    let iy: usize = ya.shape[axis..].iter().product();
6481    let mut data = Data::empty(dt);
6482    for o in 0..outer {
6483        for k in 0..ix {
6484            push_elem(&mut data, &xd, o * ix + k);
6485        }
6486        for k in 0..iy {
6487            push_elem(&mut data, &yd, o * iy + k);
6488        }
6489    }
6490    let mut shape = xa.shape.clone();
6491    shape[axis] = xa.shape[axis] + ya.shape[axis];
6492    Ok(Array::new(shape, data))
6493}
6494
6495/// `x # y` / `x / y`: item i of y appears x[i] times.
6496///
6497/// A scalar x applies to every item, and a SCALAR y is extended to as many
6498/// items as x has counts — a one-item vector is not, which is why
6499/// `1 0 1 # 5` is `5 5` and `1 0 1 # ,5` is a length error. A negative
6500/// count is APL's: it contributes that many fills. J has no such reading
6501/// and refuses it.
6502fn copy_items(x: &Array, y: &Array, apl: bool, near: NearInt, span: Span) -> Result<Array> {
6503    let counts = x
6504        .to_i64_vec_near(near)
6505        .ok_or_else(|| Error::domain("replication counts must be integers", span))?;
6506    if !apl && counts.iter().any(|&c| c < 0) {
6507        return Err(Error::domain("replication counts must be nonnegative", span));
6508    }
6509    // A scalar right argument stands in for every count, and in APL so does
6510    // an argument of ONE item along the axis: `2 0 1/,5` is `5 5 5`, where
6511    // J's `#` calls the same pair a length error.
6512    let one_item = apl && x.rank() > 0 && y.rank() > 0 && y.items() == 1 && counts.len() != 1;
6513    let scalar_y = y.rank() == 0 || one_item;
6514    let m = y.item_size();
6515    let n = if x.rank() == 0 || !scalar_y { y.items() } else { counts.len() };
6516    let per = if x.rank() == 0 { vec![counts[0]; n] } else { counts };
6517    if per.len() != n {
6518        return Err(Error::new(
6519            ErrorKind::Length,
6520            format!("{} replication count(s) for {n} item(s)", per.len()),
6521            Some(span),
6522        ));
6523    }
6524    // Items, not elements: an item of zero elements still costs a trip
6525    // round the loop, so the ceiling applies to whichever is larger.
6526    let items: u128 = per.iter().map(|&c| c.unsigned_abs() as u128).sum();
6527    let total = crate::limits::count(items * m.max(1) as u128, span)? / m.max(1);
6528    let fill = if apl { prototype_of(y) } else { None };
6529    let mut data = Data::empty(y.dtype());
6530    for (i, &c) in per.iter().enumerate() {
6531        // A scalar y stands in for every count.
6532        let src = if scalar_y { 0 } else { i };
6533        for _ in 0..c.unsigned_abs() {
6534            for k in 0..m {
6535                if c < 0 {
6536                    push_gap(&mut data, &fill);
6537                } else {
6538                    push_elem(&mut data, &y.data, src * m + k);
6539                }
6540            }
6541        }
6542    }
6543    // A scalar argument has one item, so replicating it yields a vector; an
6544    // extended one-item argument keeps the shape it already had.
6545    let mut shape = if y.rank() == 0 { vec![1] } else { y.shape.clone() };
6546    shape[0] = total;
6547    Ok(keep_proto(Array::new(shape, data), y, apl))
6548}
6549
6550/// `": y` / `⍕ y`: the argument as the characters that display it.
6551///
6552/// Characters are already their own display, so they pass through unchanged.
6553/// Anything else is laid out exactly as the session would print it: a rank-0
6554/// or rank-1 argument gives one character vector, and a higher-rank one gives
6555/// the display's lines as the rows of a character array of the same rank —
6556/// column widths span the whole argument, so every line has one width and the
6557/// planes stay aligned with each other.
6558fn format_chars(y: &Array, opts: &FmtOpts) -> Array {
6559    // A sparse array's display is a table of lines whatever its own rank
6560    // is: one line per stored entry.
6561    if y.is_sparse() {
6562        let text = crate::fmt::format_array(y, opts);
6563        let lines: Vec<&str> = text.lines().collect();
6564        let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
6565        let mut chars: Vec<char> = Vec::with_capacity(lines.len() * width);
6566        for line in &lines {
6567            chars.extend(line.chars());
6568            chars.resize(chars.len() + width - line.chars().count(), ' ');
6569        }
6570        return Array::new(vec![lines.len(), width], Data::Char(chars.into()));
6571    }
6572    if y.dtype() == DType::Char {
6573        return y.clone();
6574    }
6575    // An empty argument has nothing to lay out; J keeps its shape.
6576    if y.count() == 0 {
6577        return Array::new(y.shape.clone(), Data::empty(DType::Char));
6578    }
6579    let text = crate::fmt::format_array(y, opts);
6580    if y.dtype() == DType::Box {
6581        // A fenced box (J) takes several lines per row of cells, so the
6582        // display's own rows and columns become the last two axes of the
6583        // result. A spaced one (APL) still prints one line per row, and
6584        // keeps the plain rule below.
6585        let lines = text.lines().filter(|l| !l.is_empty()).count();
6586        let rows: usize =
6587            if y.rank() == 0 { 1 } else { y.shape[..y.rank() - 1].iter().product() };
6588        if lines != rows {
6589            return text_planes(&text, &y.shape[..y.rank().saturating_sub(2)]);
6590        }
6591    }
6592    if y.rank() < 2 {
6593        let chars: Vec<char> = text.chars().collect();
6594        return Array::new(vec![chars.len()], Data::Char(chars.into()));
6595    }
6596    // The blank lines are the plane separators, which the array does not
6597    // carry: its own shape already says where the planes are.
6598    let lines: Vec<&str> = text.lines().filter(|l| !l.is_empty()).collect();
6599    let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
6600    let mut chars: Vec<char> = Vec::with_capacity(lines.len() * width);
6601    for line in &lines {
6602        chars.extend(line.chars());
6603        chars.resize(chars.len() + width - line.chars().count(), ' ');
6604    }
6605    // One line per row of the display: the argument's shape with its last
6606    // axis replaced by the line width.
6607    let mut shape = y.shape[..y.rank() - 1].to_vec();
6608    shape.push(width);
6609    debug_assert_eq!(lines.len(), shape[..shape.len() - 1].iter().product::<usize>());
6610    Array::new(shape, Data::Char(chars.into()))
6611}
6612
6613/// A multi-line display as a character array: the frame, then the lines of
6614/// one plane, then their common width.
6615fn text_planes(text: &str, frame: &[usize]) -> Array {
6616    let lines: Vec<&str> = text.lines().filter(|l| !l.is_empty()).collect();
6617    let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
6618    let planes: usize = frame.iter().product::<usize>().max(1);
6619    let per = lines.len() / planes;
6620    let mut chars: Vec<char> = Vec::with_capacity(lines.len() * width);
6621    for line in &lines {
6622        chars.extend(line.chars());
6623        chars.resize(chars.len() + width - line.chars().count(), ' ');
6624    }
6625    let mut shape = frame.to_vec();
6626    shape.push(per);
6627    shape.push(width);
6628    Array::new(shape, Data::Char(chars.into()))
6629}
6630
6631/// Numeric data as f64, refusing characters.
6632fn digits_of(a: &Array, what: &str, span: Span) -> Result<Vec<f64>> {
6633    a.to_f64_vec().ok_or_else(|| Error::domain(format!("{what} needs numeric data"), span))
6634}
6635
6636/// Narrow a finished digit or value buffer back to integers when the inputs
6637/// were whole and nothing left the exact range, which is what both languages
6638/// do with integer arguments.
6639fn narrow(values: Vec<f64>, integral: bool) -> Data {
6640    if integral && values.iter().all(|&v| v.fract() == 0.0 && fits_i64(v)) {
6641        return Data::I64(values.iter().map(|&v| v as i64).collect::<Vec<_>>().into());
6642    }
6643    Data::F64(values.into())
6644}
6645
6646/// True when the array holds whole numbers only.
6647fn is_integral(a: &Array) -> bool {
6648    !matches!(a.dtype(), DType::F64 | DType::Rat | DType::Char | DType::Symbol)
6649}
6650
6651/// The decode of exact digits in exact radices, accumulated in the exact
6652/// types. Whole numbers keep every digit — a 19-digit integer decoded
6653/// through f64 loses its last two — and rational digits give a rational
6654/// answer, which is what J reports for `#. 1r2 1r3`. `None` hands the pass
6655/// back to the float path, which also reports the length errors.
6656fn decode_exact(x: Option<&Array>, y: &Array) -> Option<Array> {
6657    let yr = y.to_row_major();
6658    let digits = to_rat_vec(&yr.data)?;
6659    let two = Rat::from_int(Ext::from(2));
6660    let mut digits = digits;
6661    let radix: Vec<Rat> = match x {
6662        None => vec![two; digits.len()],
6663        Some(x) => {
6664            let r = to_rat_vec(&x.to_row_major().data)?;
6665            // An ATOM of digits is the digit in every position: J reads
6666            // `2 7 1 8 #. 123x` as four 123s. A one-item LIST is not an
6667            // atom and does not spread, which is why `1 2 3 #. ,5` is a
6668            // length error where `1 2 3 #. 5` is 50.
6669            if y.rank() == 0 && r.len() != 1 {
6670                digits = vec![digits[0].clone(); r.len()];
6671            }
6672            match r.len() {
6673                1 => vec![r[0].clone(); digits.len()],
6674                n if n == digits.len() => r,
6675                _ => return None,
6676            }
6677        }
6678    };
6679    let mut acc = Rat::from_int(Ext::from(0));
6680    for (d, b) in digits.iter().zip(&radix) {
6681        acc = acc.mul(b).add(d);
6682    }
6683    let exact_in = |a: &Array| matches!(a.dtype(), DType::Ext | DType::Rat);
6684    if exact_in(y) || x.is_some_and(exact_in) {
6685        return Some(Array::new(Vec::new(), exact_data(DType::Ext, vec![acc])));
6686    }
6687    // Plain integers in, a plain integer out — but only while it fits; the
6688    // float path widens beyond that, as both references do.
6689    let whole = acc.to_int()?;
6690    Some(Array::scalar_i64(exact::ext_to_i64(&whole)?))
6691}
6692
6693/// `x #. y` / `x ⊥ y`: the digits y read in the radices x. A scalar x is the
6694/// radix of every position; otherwise the two have the same length.
6695fn decode(x: Option<&Array>, y: &Array, tol: Tol, span: Span) -> Result<Array> {
6696    if let Some(exact) = decode_exact(x, y) {
6697        return Ok(exact);
6698    }
6699    let mut digits = digits_of(y, "decode", span)?;
6700    let radix: Vec<f64> = match x {
6701        None => vec![2.0; digits.len()],
6702        Some(x) => {
6703            let r = digits_of(x, "decode", span)?;
6704            // An atom of digits fills every position the radices name;
6705            // `(i. 0) #. 5` is the empty sum, 0.
6706            if y.rank() == 0 && r.len() != 1 {
6707                digits = vec![digits[0]; r.len()];
6708            }
6709            match r.len() {
6710                1 => vec![r[0]; digits.len()],
6711                n if n == digits.len() => r,
6712                n => {
6713                    return Err(Error::new(
6714                        ErrorKind::Length,
6715                        format!("{n} radices for {} digits", digits.len()),
6716                        Some(span),
6717                    ));
6718                }
6719            }
6720        }
6721    };
6722    let mut acc = 0.0f64;
6723    for (d, b) in digits.iter().zip(&radix) {
6724        // The dialect's product, so that an infinite radix meets the same
6725        // zero-factor rule `*` does: `_ #. 2` is 2, because the running
6726        // total is still zero when the infinity multiplies it.
6727        acc = tol.mul(acc, *b) + d;
6728    }
6729    let integral = is_integral(y) && x.is_none_or(is_integral);
6730    Ok(Array::new(vec![], narrow(vec![acc], integral)))
6731}
6732
6733/// `x ⊥ y` on arguments of rank 2 and above: the inner product `+.×` over
6734/// the LAST axis of x and the LEADING axis of y. A scalar x is the radix
6735/// for every digit, as it is for a vector argument.
6736fn decode_apl(x: &Array, y: &Array, span: Span) -> Result<Array> {
6737    // With no digit to weigh, no radix is ever read and none is refused:
6738    // `'a'⊥(0⍴0)` is the empty sum, 0. The zeros stand in for a radix list
6739    // the loop below never reaches.
6740    let empty = y.count() == 0;
6741    let mut digits = if empty { Vec::new() } else { digits_of(y, "decode", span)? };
6742    let radices = if empty { vec![0.0; x.count()] } else { digits_of(x, "decode", span)? };
6743    // The digit axis is y's leading one; a scalar y has one digit. The
6744    // frames are the counts of the axes the digit axis leaves over, and a
6745    // count is a product of axis lengths rather than a division: an axis of
6746    // length zero on either side leaves no elements to divide by.
6747    let mut k = if y.rank() == 0 { 1 } else { y.shape[0] };
6748    let mut n: usize = if y.rank() == 0 { 1 } else { y.shape[1..].iter().product() };
6749    let (rows, width) = match x.rank() {
6750        0 => (1usize, 0usize),
6751        r => (x.shape[..r - 1].iter().product(), x.shape[r - 1]),
6752    };
6753    // A SINGLE digit stands in every position the radices name, whatever
6754    // rank it is written at: `1 2 3⊥5`, `1 2 3⊥,5` and `1 2 3⊥1 1⍴5` are
6755    // all 50. That is APL2's single extension, and it is why only a digit
6756    // axis of some OTHER length is a length error.
6757    if y.count() == 1 && width > 1 && width != k {
6758        digits = vec![digits[0]; width];
6759        k = width;
6760        n = 1;
6761    }
6762    // A single radix spreads the same way (`(,2)⊥1 2 3` is 11), and an
6763    // empty axis on either side weighs nothing at all: the answer is the
6764    // empty sum, which is what `1 2⊥''` and `(⍳0)⊥5` both report.
6765    if width > 1 && k != 0 && width != k {
6766        return Err(Error::new(
6767            ErrorKind::Length,
6768            format!("{width} radices for {k} digits"),
6769            Some(span),
6770        ));
6771    }
6772    // A radix axis of length zero weighs nothing: every answer is the empty
6773    // sum, whatever the digits are. Only a SCALAR x spreads its one radix
6774    // over all k digits.
6775    let per_row = if x.rank() > 0 && width == 0 { 0 } else { k };
6776    let mut out = vec![0.0f64; rows * n];
6777    for i in 0..rows {
6778        for j in 0..n {
6779            let mut acc = 0.0f64;
6780            for d in 0..per_row {
6781                let b = if width <= 1 { radices[i * width] } else { radices[i * width + d] };
6782                acc = acc * b + digits[d * n + j];
6783            }
6784            out[i * n + j] = acc;
6785        }
6786    }
6787    let mut shape: Vec<usize> = if x.rank() == 0 {
6788        Vec::new()
6789    } else {
6790        x.shape[..x.rank() - 1].to_vec()
6791    };
6792    if y.rank() > 0 {
6793        shape.extend_from_slice(&y.shape[1..]);
6794    }
6795    // A radix that was never read says nothing about the answer's type: the
6796    // empty sum is the integer 0 whatever the radix was written as.
6797    let integral = is_integral(y) && (empty || is_integral(x));
6798    Ok(Array::new(shape, narrow(out, integral)))
6799}
6800
6801/// `x ⊤ y` where x has rank 2 or more: x's LEADING axis is the radix and
6802/// its remaining axes frame the answer, so the result is shaped `(⍴x), ⍴y`.
6803fn encode_apl(x: &Array, y: &Array, tol: Tol, span: Span) -> Result<Array> {
6804    // With no value to write, no radix is ever divided by and none is
6805    // refused: `'a'⊤(0⍴0)` is the empty, shaped `(⍴x),⍴y`.
6806    let empty = y.count() == 0;
6807    let radices = if empty { vec![0.0; x.count()] } else { digits_of(x, "encode", span)? };
6808    let values = if empty { Vec::new() } else { digits_of(y, "encode", span)? };
6809    let k = if x.rank() == 0 { 1 } else { x.shape[0] };
6810    let frames = if k == 0 { 0 } else { radices.len() / k };
6811    let n = values.len();
6812    let mut out = vec![0.0f64; k * frames * n];
6813    let mut radix = vec![0.0f64; k];
6814    let mut cell = vec![0.0f64; k];
6815    for p in 0..frames {
6816        for (i, r) in radix.iter_mut().enumerate() {
6817            *r = radices[i * frames + p];
6818        }
6819        for (j, &v) in values.iter().enumerate() {
6820            encode_one(&radix, v, &mut cell, tol);
6821            for i in 0..k {
6822                out[(i * frames + p) * n + j] = cell[i];
6823            }
6824        }
6825    }
6826    let mut shape = x.shape.clone();
6827    shape.extend_from_slice(&y.shape);
6828    Ok(Array::new(shape, narrow(out, empty || (is_integral(x) && is_integral(y)))))
6829}
6830
6831/// The number of binary digits `#: y` uses: enough for the largest magnitude
6832/// in the whole argument, and never fewer than one.
6833fn bit_width(values: &[f64], span: Span) -> Result<usize> {
6834    // Nothing to encode needs no digits at all: `$ #: i. 0` is `0 0`.
6835    if values.is_empty() {
6836        return Ok(0);
6837    }
6838    let mut m = 0.0f64;
6839    for &v in values {
6840        if !v.is_finite() {
6841            return Err(Error::domain("cannot encode an infinite value", span));
6842        }
6843        m = m.max(v.abs());
6844    }
6845    let whole = m.floor();
6846    if whole >= 1e15 {
6847        return Err(Error::domain("the value is too large to encode in binary", span));
6848    }
6849    let mut w = 1usize;
6850    let mut n = whole as i64;
6851    while n > 1 {
6852        n /= 2;
6853        w += 1;
6854    }
6855    Ok(w)
6856}
6857
6858/// One value written in the radices `radix`, most significant first. A radix
6859/// of 0 takes whatever is left, which is how both languages spell "and the
6860/// rest".
6861///
6862/// Each digit is a residue, and it is taken with the dialect's tolerance as
6863/// `|` itself is: `2 2 #: 4 - 1e_14` is `0 0` in jconsole, not the `1 2` an
6864/// exact quotient leaves.
6865fn encode_one(radix: &[f64], v: f64, out: &mut [f64], tol: Tol) {
6866    let mut rem = v;
6867    for i in (0..radix.len()).rev() {
6868        let b = radix[i];
6869        if b == 0.0 {
6870            out[i] = rem;
6871            rem = 0.0;
6872        } else {
6873            let r = tol.residue(b, rem);
6874            out[i] = r;
6875            rem = (rem - r) / b;
6876        }
6877    }
6878}
6879
6880/// `x #: y` / `x ⊤ y`: the digits become the LEADING axis, so the result has
6881/// shape `(#x), $y`. J applies this per atom of y (right rank 0) and APL to
6882/// the whole of it (right rank infinite); the operation itself is the same.
6883fn encode(x: &Array, y: &Array, tol: Tol, span: Span) -> Result<Array> {
6884    let radix = digits_of(x, "encode", span)?;
6885    let values = digits_of(y, "encode", span)?;
6886    let k = radix.len();
6887    let n = values.len();
6888    let mut out = vec![0.0f64; k * n];
6889    let mut cell = vec![0.0f64; k];
6890    for (j, &v) in values.iter().enumerate() {
6891        encode_one(&radix, v, &mut cell, tol);
6892        // Each digit is a residue, so a digit with no value is refused
6893        // where the residue itself would be: `5 #: _` has none.
6894        if cell.iter().any(|&d| tol.made_nan(d, v, 0.0)) {
6895            return Err(Error::nan(
6896                format!("`{}` has no digits in this base", j_number(v)),
6897                span,
6898            ));
6899        }
6900        for i in 0..k {
6901            out[i * n + j] = cell[i];
6902        }
6903    }
6904    // The digit axis is x's own shape: a scalar radix adds no axis at all,
6905    // which is why `2 #: 5` is a scalar and `2 2 #: 5` is a two-element list.
6906    let mut shape = if x.rank() == 0 { Vec::new() } else { vec![k] };
6907    shape.extend_from_slice(&y.shape);
6908    Ok(Array::new(shape, narrow(out, is_integral(x) && is_integral(y))))
6909}
6910
6911/// `#: y`: base-2 encode of the whole argument, the digits trailing.
6912fn encode_bits(y: &Array, tol: Tol, span: Span) -> Result<Array> {
6913    let values = digits_of(y, "encode", span)?;
6914    let k = bit_width(&values, span)?;
6915    let radix = vec![2.0; k];
6916    let mut out = vec![0.0f64; values.len() * k];
6917    for (j, &v) in values.iter().enumerate() {
6918        encode_one(&radix, v, &mut out[j * k..(j + 1) * k], tol);
6919    }
6920    let mut shape = y.shape.clone();
6921    shape.push(k);
6922    Ok(Array::new(shape, narrow(out, is_integral(y))))
6923}
6924
6925/// `x ,: y`: the two arguments as the items of a new leading axis. A scalar
6926/// spreads over the other argument's shape, and two scalars become
6927/// one-element lists (`1 ,: 2` has shape 2 1); otherwise the framing
6928/// machinery's own fill brings the two cells to a common shape.
6929fn laminate(x: &Array, y: &Array, span: Span) -> Result<Array> {
6930    let spread = |a: &Array, other: &Array| -> Array {
6931        if a.rank() != 0 {
6932            return a.clone();
6933        }
6934        let shape = if other.rank() == 0 { vec![1] } else { other.shape.clone() };
6935        let n: usize = shape.iter().product();
6936        let mut data = Data::empty(a.dtype());
6937        for _ in 0..n {
6938            push_elem(&mut data, &a.data, 0);
6939        }
6940        Array::new(shape, data)
6941    };
6942    assemble(&[2], vec![spread(x, y), spread(y, x)], span)
6943}
6944
6945/// `⍪ y`: one row per item, holding that item's elements.
6946fn table_of(y: &Array) -> Array {
6947    let shape = match y.rank() {
6948        0 => vec![1, 1],
6949        _ => vec![y.items(), y.item_size()],
6950    };
6951    Array::new(shape, y.data.clone())
6952}
6953
6954/// One application of the APL kind: between two ITEMS.
6955///
6956/// APL hands a function the contents of an item, not the item, and puts a
6957/// result that is not a simple scalar back under an enclosure so it can take
6958/// one place in the array being built. J leaves its boxes shut instead,
6959/// which is where the two languages part on `∘.⌽` and on `,/`.
6960fn item_dyad(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
6961    let r = u.dyad(&open_cell(x), &open_cell(y), ctx, span)?;
6962    Ok(enclose(&r, Enclose::ExceptSimpleScalar))
6963}
6964
6965/// `x ∘.u y` (APL): u between every element of x and every element of y.
6966///
6967/// The elements are atoms whatever u's rank — `1 2∘.,3 4` is a 2-by-2 table
6968/// of pairs, not one catenation — and each is disclosed on the way in, so
6969/// `¯1 0 1∘.⌽⊂m` rotates the matrix rather than the enclosure holding it.
6970/// The result of each application is enclosed again unless it is already a
6971/// simple scalar.
6972fn outer_product(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
6973    let mut frame = x.shape.clone();
6974    frame.extend_from_slice(&y.shape);
6975    let (nx, ny) = (x.count(), y.count());
6976    let n = nx * ny;
6977    if n == 0 {
6978        return assemble(&frame, Vec::new(), span);
6979    }
6980    let (xr, yr) = (x.to_row_major(), y.to_row_major());
6981    let cells = each_cell(n, nx.max(ny).max(n), u.is_pure(), ctx, |i, c| {
6982        item_dyad(u, &atom(&xr, i / ny), &atom(&yr, i % ny), c, span)
6983    })?;
6984    assemble_items(&frame, cells, span)
6985}
6986
6987/// `x u/ y`: u applied to every pair of cells, x's frame before y's.
6988///
6989/// The cells are the ones u's own ranks ask for, which is why `1 2 3 +/ 10 20`
6990/// is a 3-by-2 table (atoms both sides) while `x ,/ y` is a single catenation
6991/// (`,` takes its arguments whole). APL spells the same table `∘.u` and reads
6992/// it by items instead, so that is where its dyad goes.
6993fn table(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
6994    if ctx.cfg.rules.lang == crate::Lang::Apl {
6995        return outer_product(u, x, y, ctx, span);
6996    }
6997    let ranks = u.ranks();
6998    let fxl = x.rank() - effective_rank(ranks[1], x.rank());
6999    let fyl = y.rank() - effective_rank(ranks[2], y.rank());
7000    let mut frame = x.shape[..fxl].to_vec();
7001    frame.extend_from_slice(&y.shape[..fyl]);
7002    let nx: usize = x.shape[..fxl].iter().product();
7003    let ny: usize = y.shape[..fyl].iter().product();
7004    let n = nx * ny;
7005    if n == 0 {
7006        return assemble(&frame, Vec::new(), span);
7007    }
7008    if frame.is_empty() {
7009        return u.dyad(x, y, ctx, span);
7010    }
7011    let work = x.count().max(y.count()).max(n);
7012    let cells = each_cell(n, work, u.is_pure(), ctx, |i, c| {
7013        u.dyad(&x.cell_at(fxl, i / ny), &y.cell_at(fyl, i % ny), c, span)
7014    })?;
7015    assemble(&frame, cells, span)
7016}
7017
7018/// The same verb with a different index origin — APL's `f⍠('IO' n)`.
7019///
7020/// The origin is a dialect setting, resolved into the primitives when the
7021/// program is compiled, so overriding it for one application means deriving
7022/// the verb again with the other value. None where the verb has no origin
7023/// to change, which is what makes `⎕IO` not one of its options.
7024pub(crate) fn with_origin(v: &Verb, origin: i64) -> Option<Verb> {
7025    match v {
7026        Verb::Prim(p) => {
7027            let mut out = *p;
7028            let mut changed = false;
7029            out.monad = match p.monad {
7030                MonadOp::GradeUp { .. } => {
7031                    changed = true;
7032                    MonadOp::GradeUp { origin }
7033                }
7034                MonadOp::GradeDown { .. } => {
7035                    changed = true;
7036                    MonadOp::GradeDown { origin }
7037                }
7038                MonadOp::IotaApl { .. } => {
7039                    changed = true;
7040                    MonadOp::IotaApl { origin }
7041                }
7042                MonadOp::Indices { boxed_coords, .. } => {
7043                    changed = true;
7044                    MonadOp::Indices { origin, boxed_coords }
7045                }
7046                MonadOp::Roll { fixed, float_at_zero, .. } => {
7047                    changed = true;
7048                    MonadOp::Roll { origin, fixed, float_at_zero }
7049                }
7050                other => other,
7051            };
7052            out.dyad = match p.dyad {
7053                DyadOp::IndexOf { vector_left, .. } => {
7054                    changed = true;
7055                    DyadOp::IndexOf { origin, vector_left }
7056                }
7057                DyadOp::IndexOfLast { .. } => {
7058                    changed = true;
7059                    DyadOp::IndexOfLast { origin }
7060                }
7061                DyadOp::CollateGrade { down, .. } => {
7062                    changed = true;
7063                    DyadOp::CollateGrade { down, origin }
7064                }
7065                DyadOp::Squad { leading, .. } => {
7066                    changed = true;
7067                    DyadOp::Squad { origin, leading }
7068                }
7069                DyadOp::Pick { .. } => {
7070                    changed = true;
7071                    DyadOp::Pick { origin }
7072                }
7073                DyadOp::SelectAxis { axis, rank, .. } => {
7074                    changed = true;
7075                    DyadOp::SelectAxis { axis, rank, origin }
7076                }
7077                DyadOp::Deal { fixed, .. } => {
7078                    changed = true;
7079                    DyadOp::Deal { origin, fixed }
7080                }
7081                other => other,
7082            };
7083            changed.then_some(Verb::Prim(out))
7084        }
7085        Verb::Rank(u, r) => Some(Verb::Rank(Box::new(with_origin(u, origin)?), *r)),
7086        Verb::Reduce(u) => Some(Verb::Reduce(Box::new(with_origin(u, origin)?))),
7087        Verb::NWise(u) => Some(Verb::NWise(Box::new(with_origin(u, origin)?))),
7088        Verb::Windowed(u, k) => Some(Verb::Windowed(Box::new(with_origin(u, origin)?), *k)),
7089        Verb::Commute(u) => Some(Verb::Commute(Box::new(with_origin(u, origin)?))),
7090        Verb::Each(u, e) => Some(Verb::Each(Box::new(with_origin(u, origin)?), *e)),
7091        Verb::Fit(u, n) => Some(Verb::Fit(Box::new(with_origin(u, origin)?), *n)),
7092        Verb::AlongAxis(u, k) => Some(Verb::AlongAxis(Box::new(with_origin(u, origin)?), *k)),
7093        _ => None,
7094    }
7095}
7096
7097// ------------------------------------------------------- inner product
7098
7099/// The scalar operation a bare primitive performs dyadically, for the fast
7100/// paths that recognise `+` and `*` rather than applying them.
7101fn scalar_dyad_of(v: &Verb) -> Option<ScalarDyad> {
7102    match v {
7103        Verb::Prim(p) => match p.dyad {
7104            DyadOp::Scalar(op) => Some(op),
7105            _ => None,
7106        },
7107        _ => None,
7108    }
7109}
7110
7111/// True where the verb folds a list with one scalar operation, which is
7112/// what `+/` and `∧/` are and what the matrix product's fast path needs.
7113fn folds_with(u: &Verb, op: ScalarDyad) -> bool {
7114    matches!(u, Verb::Reduce(inner) if scalar_dyad_of(inner) == Some(op))
7115}
7116
7117/// `x u . v y`: the inner product.
7118///
7119/// x is taken in cells at v's dyadic left rank, or at rank 1 where that is
7120/// smaller — the rule that makes `+/ . *` a matrix product and leaves a
7121/// whole-argument v (`,`, `,:`) reading the whole of x. Each cell meets the
7122/// WHOLE of y under v, and u folds what comes back.
7123fn inner_product(
7124    u: &Verb,
7125    v: &Verb,
7126    apl: bool,
7127    x: &Array,
7128    y: &Array,
7129    ctx: &mut Ctx<'_>,
7130    span: Span,
7131) -> Result<Array> {
7132    if let Some(a) = matrix_product(u, v, x, y, span) {
7133        return Ok(a);
7134    }
7135    // APL pairs each row of x with each COLUMN of y, which parts from J's
7136    // reading exactly where v does not apply to atoms.
7137    if apl && scalar_dyad_of(v).is_none() {
7138        return apl_inner_product(u, v, x, y, ctx, span);
7139    }
7140    if !apl {
7141        return inner_cells(u, v, false, x, y, ctx, span);
7142    }
7143    // A scalar v pairs one element of the row with one element of the
7144    // column, which is the leading-axis pairing J spells out and APL's own
7145    // conformability rule — about whole applications — does not describe.
7146    // The definition asks for that pairing, so the inner application runs
7147    // under it and the caller's rule is put back afterwards.
7148    let saved = ctx.cfg.agreement;
7149    ctx.cfg.agreement = Agreement::LeadingPrefix;
7150    let out = inner_cells(u, v, true, x, y, ctx, span);
7151    ctx.cfg.agreement = saved;
7152    out
7153}
7154
7155/// Every element enclosed once more, which is what an each does to the
7156/// values it brings back. A simple array is all simple scalars and cannot
7157/// be nested any further, so it is returned as it stands.
7158fn enclose_elements(a: &Array) -> Array {
7159    if a.dtype() == DType::Box { boxed_elements(a) } else { a.clone() }
7160}
7161
7162/// The fold that closes the cells of an inner product.
7163///
7164/// APL's definition is `f/¨ (⊂[last]x) ∘.g (⊂[first]y)`: the each is part of
7165/// it, so what the fold makes of one pair is enclosed unless it is already a
7166/// simple scalar. `1 2+.×3 4` is a number either way; `1 2,.+3 4` is an
7167/// enclosed vector, and only APL says so.
7168fn inner_fold(
7169    u: &Verb,
7170    apl: bool,
7171    inner: &Array,
7172    ctx: &mut Ctx<'_>,
7173    span: Span,
7174) -> Result<Array> {
7175    let folded = u.monad(inner, ctx, span)?;
7176    Ok(if apl { enclose_elements(&folded) } else { folded })
7177}
7178
7179/// The inner product by the cell machinery: x's cells at v's dyadic left
7180/// rank, or at rank 1 where that is smaller, each against the whole of y.
7181fn inner_cells(
7182    u: &Verb,
7183    v: &Verb,
7184    apl: bool,
7185    x: &Array,
7186    y: &Array,
7187    ctx: &mut Ctx<'_>,
7188    span: Span,
7189) -> Result<Array> {
7190    let cell_rank = effective_rank(v.ranks()[1].max(1), x.rank());
7191    let frame_rank = x.rank() - cell_rank;
7192    if frame_rank == 0 {
7193        let inner = v.dyad(x, y, ctx, span)?;
7194        return inner_fold(u, apl, &inner, ctx, span);
7195    }
7196    let frame = x.shape[..frame_rank].to_vec();
7197    let n: usize = frame.iter().product();
7198    if n == 0 {
7199        return assemble(&frame, Vec::new(), span);
7200    }
7201    let work = x.count().max(y.count());
7202    let pure = u.is_pure() && v.is_pure();
7203    let cells = each_cell(n, work, pure, ctx, |i, c| {
7204        let inner = v.dyad(&x.cell_at(frame_rank, i), y, c, span)?;
7205        inner_fold(u, apl, &inner, c, span)
7206    })?;
7207    assemble(&frame, cells, span)
7208}
7209
7210/// APL's `f.g` where g is not a scalar function: every vector along x's
7211/// LAST axis meets every vector along y's FIRST axis, and f folds each
7212/// result. With a scalar g this is the same as J's reading, which is the
7213/// path that runs it.
7214fn apl_inner_product(
7215    u: &Verb,
7216    v: &Verb,
7217    x: &Array,
7218    y: &Array,
7219    ctx: &mut Ctx<'_>,
7220    span: Span,
7221) -> Result<Array> {
7222    // A scalar argument stands for as many copies of itself as the other
7223    // side's shared axis asks for; two scalars share an axis of one.
7224    let k = match (x.rank(), y.rank()) {
7225        (0, 0) => 1,
7226        (0, _) => y.shape[0],
7227        _ => x.shape[x.rank() - 1],
7228    };
7229    if x.rank() > 0 && y.rank() > 0 && x.shape[x.rank() - 1] != y.shape[0] {
7230        return Err(Error::new(
7231            ErrorKind::Length,
7232            format!("inner product over {} and {} elements", x.shape[x.rank() - 1], y.shape[0]),
7233            Some(span),
7234        ));
7235    }
7236    let lead: &[usize] = if x.rank() > 0 { &x.shape[..x.rank() - 1] } else { &[] };
7237    let trail: &[usize] = if y.rank() > 0 { &y.shape[1..] } else { &[] };
7238    let rows: usize = lead.iter().product();
7239    let cols: usize = trail.iter().product();
7240    let mut frame = lead.to_vec();
7241    frame.extend_from_slice(trail);
7242    let n = rows * cols;
7243    if n == 0 {
7244        return assemble(&frame, Vec::new(), span);
7245    }
7246    let vector = |d: &Data, at: &dyn Fn(usize) -> usize| {
7247        let mut out = Data::empty(d.dtype());
7248        for t in 0..k {
7249            out.push_from(d, at(t));
7250        }
7251        Array::new(vec![k], out)
7252    };
7253    let pure = u.is_pure() && v.is_pure();
7254    let cells = each_cell(n, x.count().max(y.count()), pure, ctx, |i, c| {
7255        let (r, col) = (i / cols, i % cols);
7256        let left = vector(&x.data, &|t| if x.rank() > 0 { r * k + t } else { 0 });
7257        let right = vector(&y.data, &|t| if y.rank() > 0 { t * cols + col } else { 0 });
7258        let inner = v.dyad(&left, &right, c, span)?;
7259        inner_fold(u, true, &inner, c, span)
7260    })?;
7261    assemble(&frame, cells, span)
7262}
7263
7264/// `+/ . *` (APL `+.×`) over real machine numbers: the matrix product, run
7265/// as a blocked pass over the two buffers instead of by the cell machinery.
7266/// The shape rule is the general one — x's last axis pairs with y's first —
7267/// so an argument of any rank comes through here. None sends the
7268/// application back to the general path.
7269fn matrix_product(u: &Verb, v: &Verb, x: &Array, y: &Array, span: Span) -> Option<Array> {
7270    if !folds_with(u, ScalarDyad::Add) || scalar_dyad_of(v) != Some(ScalarDyad::Mul) {
7271        return None;
7272    }
7273    if x.rank() == 0 || y.rank() == 0 {
7274        return None;
7275    }
7276    let k = x.shape[x.rank() - 1];
7277    if k != y.shape[0] {
7278        return None;
7279    }
7280    let rows: usize = x.shape[..x.rank() - 1].iter().product();
7281    let cols: usize = y.shape[1..].iter().product();
7282    let mut shape = x.shape[..x.rank() - 1].to_vec();
7283    shape.extend_from_slice(&y.shape[1..]);
7284    if crate::limits::elements(&shape, span).is_err() {
7285        return None;
7286    }
7287    let whole = matches!(x.dtype(), DType::Bool | DType::I64)
7288        && matches!(y.dtype(), DType::Bool | DType::I64);
7289    if whole
7290        && let (Some(xs), Some(ys)) = (x.to_i64_vec(), y.to_i64_vec())
7291        && let Some(out) = matmul_whole(&xs, &ys, rows, k, cols)
7292    {
7293        return Some(Array::new(shape, Data::I64(out.into())));
7294    }
7295    let (xs, ys) = (x.to_f64_vec()?, y.to_f64_vec()?);
7296    let out = par::fill_rows(rows, cols, rows * k * cols, |r0, part| {
7297        matmul_f64(&xs, &ys, k, cols, r0, part);
7298    });
7299    Some(Array::new(shape, Data::F64(out.into())))
7300}
7301
7302/// Elements a block of the matrix product's inner axis covers at once: the
7303/// slice of y one pass over the output rows reuses. 128 rows of a 1000-wide
7304/// table is a megabyte, which is what a second-level cache holds.
7305const MATMUL_BLOCK: usize = 128;
7306
7307#[inline(always)]
7308fn matmul_f64_body(xs: &[f64], ys: &[f64], k: usize, n: usize, r0: usize, out: &mut [f64]) {
7309    if n == 0 {
7310        return;
7311    }
7312    let rows = out.len() / n;
7313    for k0 in (0..k).step_by(MATMUL_BLOCK) {
7314        let k1 = (k0 + MATMUL_BLOCK).min(k);
7315        for r in 0..rows {
7316            let left = &xs[(r0 + r) * k..(r0 + r + 1) * k];
7317            let dst = &mut out[r * n..(r + 1) * n];
7318            for (t, &a) in left.iter().enumerate().take(k1).skip(k0) {
7319                let row = &ys[t * n..(t + 1) * n];
7320                for (o, &b) in dst.iter_mut().zip(row) {
7321                    *o += a * b;
7322                }
7323            }
7324        }
7325    }
7326}
7327
7328multiversioned! {
7329    /// One block of output rows of a float matrix product. `out` is the
7330    /// block, `r0` the row it starts at; the accumulator is the output
7331    /// itself, which arrives zeroed.
7332    fn matmul_f64(
7333        xs: &[f64],
7334        ys: &[f64],
7335        k: usize,
7336        n: usize,
7337        r0: usize,
7338        out: &mut [f64],
7339    ) -> () = matmul_f64_body;
7340}
7341
7342#[inline(always)]
7343fn matmul_i64_body(xs: &[i64], ys: &[i64], k: usize, n: usize, r0: usize, out: &mut [i64]) {
7344    if n == 0 {
7345        return;
7346    }
7347    let rows = out.len() / n;
7348    for k0 in (0..k).step_by(MATMUL_BLOCK) {
7349        let k1 = (k0 + MATMUL_BLOCK).min(k);
7350        for r in 0..rows {
7351            let left = &xs[(r0 + r) * k..(r0 + r + 1) * k];
7352            let dst = &mut out[r * n..(r + 1) * n];
7353            for (t, &a) in left.iter().enumerate().take(k1).skip(k0) {
7354                let row = &ys[t * n..(t + 1) * n];
7355                for (o, &b) in dst.iter_mut().zip(row) {
7356                    *o = o.wrapping_add(a.wrapping_mul(b));
7357                }
7358            }
7359        }
7360    }
7361}
7362
7363multiversioned! {
7364    /// One block of output rows of an integer matrix product. Reached only
7365    /// where the values cannot overflow, so wrapping arithmetic is exact
7366    /// arithmetic here and the loop vectorises.
7367    fn matmul_i64(
7368        xs: &[i64],
7369        ys: &[i64],
7370        k: usize,
7371        n: usize,
7372        r0: usize,
7373        out: &mut [i64],
7374    ) -> () = matmul_i64_body;
7375}
7376
7377/// The same product over integers. None where a product or a sum leaves
7378/// i64, which sends the whole pass to floats, as every other integer
7379/// primitive does.
7380fn matmul_whole(xs: &[i64], ys: &[i64], rows: usize, k: usize, n: usize) -> Option<Vec<i64>> {
7381    // A bound on the largest partial sum decides once, for the whole pass,
7382    // whether the plain loop can overflow at all. Where it cannot, the
7383    // vectorised kernel runs; where it might, the checked loop does, and
7384    // leaving i64 anywhere sends the whole product to floats.
7385    let bound = |v: &[i64]| v.iter().map(|&a| (a as i128).abs()).max().unwrap_or(0);
7386    if bound(xs).saturating_mul(bound(ys)).saturating_mul(k as i128) <= i64::MAX as i128 {
7387        return Some(par::fill_rows(rows, n, rows * k * n, |r0, part| {
7388            matmul_i64(xs, ys, k, n, r0, part);
7389        }));
7390    }
7391    let mut out = vec![0i64; rows * n];
7392    for r in 0..rows {
7393        let left = &xs[r * k..(r + 1) * k];
7394        let dst = &mut out[r * n..(r + 1) * n];
7395        for (t, &a) in left.iter().enumerate() {
7396            for (o, &b) in dst.iter_mut().zip(&ys[t * n..(t + 1) * n]) {
7397                *o = a.checked_mul(b).and_then(|p| o.checked_add(p))?;
7398            }
7399        }
7400    }
7401    Some(out)
7402}
7403
7404/// Rows a determinant by minors is computed for at most. The recursion is
7405/// memoised on the set of rows still in play, so the cost is `2^n` cells
7406/// rather than `n!` — but it is still exponential, and past this the
7407/// message names the limit instead of running out of memory.
7408const DETERMINANT_MINORS_MAX: usize = 16;
7409
7410/// `u . v y`: the determinant by minors down the FIRST column — for each
7411/// row in turn, that row's leading element under v with the determinant of
7412/// the table the row and the column leave behind, all folded by u. With no
7413/// columns left the value is v's identity element; with no rows left it is
7414/// u over nothing.
7415fn determinant(
7416    u: &Verb,
7417    v: &Verb,
7418    apl: bool,
7419    y: &Array,
7420    ctx: &mut Ctx<'_>,
7421    span: Span,
7422) -> Result<Array> {
7423    if apl {
7424        return Err(Error::domain("an inner product has no monadic meaning in APL", span));
7425    }
7426    // The determinant is of a table, so an argument of higher rank frames
7427    // one answer per 2-cell. Nothing above applies the rank machinery for
7428    // this verb: its dyad reads both arguments whole.
7429    if y.rank() > 2 {
7430        let frame = y.shape[..y.rank() - 2].to_vec();
7431        let n: usize = frame.iter().product();
7432        let pure = u.is_pure() && v.is_pure();
7433        let cells = each_cell(n, y.count(), pure, ctx, |i, c| {
7434            determinant(u, v, apl, &y.cell_at(y.rank() - 2, i), c, span)
7435        })?;
7436        return assemble(&frame, cells, span);
7437    }
7438    let rows = y.items();
7439    let cols = y.item_size();
7440    if folds_with(u, ScalarDyad::Sub)
7441        && scalar_dyad_of(v) == Some(ScalarDyad::Mul)
7442        && rows == cols
7443        && rows >= 3
7444        && matches!(y.dtype(), DType::Bool | DType::I64 | DType::F64)
7445        && let Some(values) = y.to_f64_vec()
7446    {
7447        return Ok(Array::scalar_f64(determinant_lu(values, rows)));
7448    }
7449    if rows > DETERMINANT_MINORS_MAX {
7450        return Err(Error::not_yet(
7451            format!(
7452                "a determinant of more than {DETERMINANT_MINORS_MAX} rows by minors \
7453                 (only -/ . * over machine numbers has a direct method)"
7454            ),
7455            span,
7456        ));
7457    }
7458    let mut seen: HashMap<u64, Array> = HashMap::new();
7459    let all = if rows == 64 { u64::MAX } else { (1u64 << rows) - 1 };
7460    minors(u, v, y, cols, rows, all, &mut seen, ctx, span)
7461}
7462
7463/// One node of the expansion: the determinant of the table `left` still
7464/// names rows of, with the leading columns the recursion has consumed
7465/// already dropped.
7466#[allow(clippy::too_many_arguments)]
7467fn minors(
7468    u: &Verb,
7469    v: &Verb,
7470    y: &Array,
7471    cols: usize,
7472    rows: usize,
7473    left: u64,
7474    seen: &mut HashMap<u64, Array>,
7475    ctx: &mut Ctx<'_>,
7476    span: Span,
7477) -> Result<Array> {
7478    if let Some(a) = seen.get(&left) {
7479        return Ok(a.clone());
7480    }
7481    // One row and one column go at every step, so how many rows are left
7482    // says which column this node starts at.
7483    let column = rows - left.count_ones() as usize;
7484    let value = if column >= cols {
7485        let data = reduce_identity(v, 1, ctx.cfg.rules.lang).ok_or_else(|| {
7486            Error::not_yet(
7487                format!("the identity element of {} (a determinant with no columns)", v.name()),
7488                span,
7489            )
7490        })?;
7491        Array::new(Vec::new(), data)
7492    } else if left == 0 {
7493        u.monad(&Array::new(vec![0], Data::empty(DType::I64)), ctx, span)?
7494    } else {
7495        let mut terms = Vec::with_capacity(left.count_ones() as usize);
7496        for r in 0..rows {
7497            if left & (1 << r) == 0 {
7498                continue;
7499            }
7500            let minor = minors(u, v, y, cols, rows, left & !(1 << r), seen, ctx, span)?;
7501            let head = Array::new(Vec::new(), y.data.slice(r * cols + column, r * cols + column + 1));
7502            terms.push(v.dyad(&head, &minor, ctx, span)?);
7503        }
7504        let n = terms.len();
7505        u.monad(&assemble(&[n], terms, span)?, ctx, span)?
7506    };
7507    seen.insert(left, value.clone());
7508    Ok(value)
7509}
7510
7511/// `-/ . * y` over machine numbers: the determinant by Gaussian
7512/// elimination with partial pivoting, which is how the reference computes
7513/// it from three rows up — and why its answer there is a float even where
7514/// every element is whole.
7515fn determinant_lu(mut a: Vec<f64>, n: usize) -> f64 {
7516    let mut det = 1.0f64;
7517    for c in 0..n {
7518        let mut pivot = c;
7519        for r in c + 1..n {
7520            if a[r * n + c].abs() > a[pivot * n + c].abs() {
7521                pivot = r;
7522            }
7523        }
7524        if a[pivot * n + c] == 0.0 {
7525            return 0.0;
7526        }
7527        if pivot != c {
7528            for j in 0..n {
7529                a.swap(c * n + j, pivot * n + j);
7530            }
7531            det = -det;
7532        }
7533        let head = a[c * n + c];
7534        det *= head;
7535        for r in c + 1..n {
7536            let factor = a[r * n + c] / head;
7537            if factor == 0.0 {
7538                continue;
7539            }
7540            for j in c..n {
7541                a[r * n + j] -= factor * a[c * n + j];
7542            }
7543        }
7544    }
7545    det
7546}
7547
7548/// Monadic meaning of a primitive, applied to one cell.
7549fn monad_op(p: &Prim, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
7550    let apl = ctx.cfg.rules.lang == crate::Lang::Apl;
7551    let out = monad_op_inner(p, y, ctx, span);
7552    if apl { out.map(tightened_mixed) } else { out }
7553}
7554
7555/// Every APL result passes through [`tightened_mixed`] on the way out, so
7556/// the mixed simple form never outlives the mixture that called for it.
7557fn monad_op_inner(p: &Prim, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
7558    match p.monad {
7559        MonadOp::Scalar(op) => scalar_monad(op, y, ctx.cfg, span),
7560        MonadOp::ShapeOf => {
7561            Ok(carry_exact(Array::from_i64(y.shape.iter().map(|&n| n as i64).collect()), y))
7562        }
7563        MonadOp::Tally => Ok(carry_exact(Array::scalar_i64(y.items() as i64), y)),
7564        MonadOp::Ravel => Ok(Array::new(vec![y.count()], y.data.clone())),
7565        // `,. y` is one row per item, the item raveled along it. An atom is
7566        // one item whose ravel is one element, so it becomes a 1-by-1
7567        // table: `$ ,. 5` is `1 1`, which is where `,"_1` alone would stop
7568        // one axis short.
7569        MonadOp::RavelItems => {
7570            let (items, width) = if y.rank() == 0 {
7571                (1usize, 1usize)
7572            } else {
7573                (y.shape[0], y.shape[1..].iter().product::<usize>())
7574            };
7575            Ok(Array::new(vec![items, width], y.to_row_major().data))
7576        }
7577        MonadOp::TransposeAxes => Ok(transpose_axes(y)),
7578        MonadOp::Head => Ok(head(y)),
7579        MonadOp::Behead => behead(y, span),
7580        MonadOp::Tail => Ok(tail(y)),
7581        MonadOp::Curtail => Ok(curtail(y)),
7582        MonadOp::Reverse => Ok(reverse(y)),
7583        // Monadic `∪` stays nub over ITEMS at any rank, which is a
7584        // recorded divergence from GNU APL's vectors-only monad.
7585        MonadOp::Nub => Ok(nub(y, ctx.cfg.tol)),
7586        MonadOp::GradeUp { origin } | MonadOp::GradeDown { origin } => {
7587            check_gradable(y, ctx.cfg.rules, span)?;
7588            // APL grades the ITEMS of an array, so a scalar has none to
7589            // grade; J answers with the one-item permutation.
7590            if ctx.cfg.rules.lang == crate::Lang::Apl && y.rank() == 0 {
7591                return Err(Error::domain("a grade needs an array, not a scalar", span));
7592            }
7593            let down = matches!(p.monad, MonadOp::GradeDown { .. });
7594            let order = grade_order(y, down, Grading::of(ctx.cfg.rules, ctx.cfg.tol));
7595            Ok(Array::from_i64(order.iter().map(|&i| origin + i as i64).collect()))
7596        }
7597        MonadOp::IotaJ => iota_j(y, ctx.cfg.near(), span),
7598        MonadOp::IotaApl { origin } => iota_apl(y, origin, ctx.cfg.near(), span),
7599        MonadOp::Echo => {
7600            (ctx.out)(&format!("{}\n", crate::fmt::format_array(y, &ctx.cfg.fmt)));
7601            Ok(Array::empty(DType::I64))
7602        }
7603        MonadOp::ReadStream => {
7604            stream_number(y, 1, "1!:1 reads", span)?;
7605            let line = ctx.read_line(span)?;
7606            Ok(Array::from_chars(line.chars().collect()))
7607        }
7608        MonadOp::TypeCode => Ok(Array::scalar_i64(type_code(y))),
7609        MonadOp::Sparse => crate::sparse::sparsify(y, span),
7610        MonadOp::Dense => Ok(y.densified()),
7611        MonadOp::PrimeCount => {
7612            let n = y
7613                .to_i64_vec_near(ctx.cfg.near())
7614                .ok_or_else(|| Error::domain("the prime count needs an integer", span))?;
7615            let v = n.first().copied().unwrap_or(0);
7616            Ok(carry_exact(Array::scalar_i64(primes_below(v, span)?), y))
7617        }
7618        MonadOp::IndicesInverse => indices_inverse(y, ctx.cfg.near(), span),
7619        MonadOp::Same => Ok(y.clone()),
7620        MonadOp::Format => Ok(format_chars(y, &ctx.cfg.fmt)),
7621        MonadOp::DecodeBits => decode(None, y, ctx.cfg.tol, span).map(|r| carry_exact(r, y)),
7622        MonadOp::EncodeBits => encode_bits(y, ctx.cfg.tol, span).map(|r| carry_exact(r, y)),
7623        MonadOp::Itemize => {
7624            let mut shape = vec![1usize];
7625            shape.extend_from_slice(&y.shape);
7626            Ok(Array::new(shape, y.data.clone()))
7627        }
7628        MonadOp::TableOf => Ok(table_of(y)),
7629        MonadOp::Enclose(rule) => Ok(enclose(y, rule)),
7630        MonadOp::Open => Ok(open_cell(y)),
7631        MonadOp::Raze => raze(y, span),
7632        MonadOp::Catalogue => catalogue(y, span),
7633        MonadOp::AtomicRep => atomic_rep(y, ctx, span),
7634        MonadOp::RazeIn => raze_in(y, ctx.cfg.tol, span),
7635        MonadOp::First => Ok(first(y)),
7636        MonadOp::Enlist => enlist(y, span),
7637        MonadOp::Depth { signed } => {
7638            let d = depth(y);
7639            Ok(Array::scalar_i64(if signed && d > 1 && !uniform(y) { -d } else { d }))
7640        }
7641        MonadOp::Indices { origin, boxed_coords } => {
7642            where_indices(y, origin, boxed_coords, ctx.cfg.near(), span)
7643        }
7644        MonadOp::Steps => steps(y, span),
7645        MonadOp::ToExact => to_exact(y, span),
7646        MonadOp::NthPrime => {
7647            let n = y
7648                .to_i64_vec_near(ctx.cfg.near())
7649                .ok_or_else(|| Error::domain("the prime index must be an integer", span))?;
7650            let v = n.first().copied().unwrap_or(0);
7651            Ok(carry_exact(Array::scalar_i64(nth_prime(v, span)?), y))
7652        }
7653        MonadOp::PrimeFactors => {
7654            let n = y
7655                .to_i64_vec_near(ctx.cfg.near())
7656                .ok_or_else(|| Error::domain("prime factors need an integer", span))?;
7657            let v = n.first().copied().unwrap_or(0);
7658            Ok(carry_exact(Array::from_i64(prime_factors(v, span)?), y))
7659        }
7660        MonadOp::MatrixInverse => matrix_inverse(y, span),
7661        MonadOp::Roll { origin, fixed, float_at_zero } => {
7662            roll(y, origin, fixed, float_at_zero, ctx.cfg.near(), span)
7663        }
7664        MonadOp::ComplexParts { polar } => complex_parts(y, polar, span),
7665        MonadOp::SelfClassify => Ok(self_classify(y, ctx.cfg.tol)),
7666        MonadOp::NubSieve => Ok(nub_sieve(y, ctx.cfg.tol, ctx.cfg.rules.lang)),
7667        MonadOp::Unicode { pass_chars } => unicode(y, pass_chars, ctx.cfg.near(), span),
7668        MonadOp::Symbols => to_symbols(y, span),
7669        MonadOp::Words => words(y, span),
7670        MonadOp::LevelOf => Ok(Array::scalar_i64(boxing_level(y))),
7671        MonadOp::MapPaths => Ok(map_paths(y)),
7672        MonadOp::Nest => Ok(nest(y)),
7673        MonadOp::PolyRoots => poly_roots(y, span),
7674        MonadOp::PolyDeriv => poly_deriv(y, span),
7675        MonadOp::AnagramIndex => anagram_index(y, ctx.cfg.rules, span),
7676        MonadOp::CycleForm => cycle_form(y, ctx.cfg.near(), span),
7677        MonadOp::Split => Ok(split_items(y)),
7678        MonadOp::Execute { apl } => execute(y, apl, ctx, span),
7679        MonadOp::NotYet(what) => Err(Error::not_yet(what, span)),
7680        MonadOp::None => {
7681            Err(Error::domain(format!("{} has no monadic meaning", p.name), span))
7682        }
7683    }
7684}
7685
7686/// Left argument of reshape/take/drop: a scalar or vector of integers.
7687/// J `+. y` and `*. y` at rank 0: one complex value as its two parts, so
7688/// the rank machinery turns them into a new trailing axis of length 2.
7689fn complex_parts(y: &Array, polar: bool, span: Span) -> Result<Array> {
7690    let Some(v) = y.to_complex_vec() else {
7691        return Err(wrong_type(y.dtype(), span));
7692    };
7693    let z = v.first().copied().unwrap_or(cx::ZERO);
7694    let pair = if polar { vec![cx::abs(z), cx::arg(z)] } else { vec![z[0], z[1]] };
7695    Ok(Array::from_f64(pair))
7696}
7697
7698fn axis_counts(x: &Array, what: &str, near: NearInt, span: Span) -> Result<Vec<i64>> {
7699    if x.rank() > 1 {
7700        return Err(Error::new(
7701            ErrorKind::Rank,
7702            format!("{what} needs a scalar or vector left argument"),
7703            Some(span),
7704        ));
7705    }
7706    // An empty left argument asks for no axes at all, whatever type it
7707    // happens to carry: `'' $ y` is y's first item, not a type error.
7708    if x.count() == 0 {
7709        return Ok(Vec::new());
7710    }
7711    x.to_i64_vec_near(near)
7712        .ok_or_else(|| Error::domain(format!("{what} needs integer lengths"), span))
7713}
7714
7715/// `x $ y` and `x ⍴ y` are not the same verb.
7716///
7717/// J lays out ITEMS: the result's shape is x followed by the shape of an
7718/// item of y, and the items are reused cyclically, so `$ 3 $ i. 3 4` is
7719/// `3 4` and `'' $ y` is y's first item. APL lays out ELEMENTS: the shape
7720/// is exactly x and y's ravel is reused. The two agree on every vector y,
7721/// which is why the difference shows only above rank 1.
7722///
7723/// An empty y parts them too: J refuses to invent items it was not given,
7724/// and APL fills with the type's fill element.
7725fn reshape(
7726    x: &Array,
7727    y: &Array,
7728    by_items: bool,
7729    apl: bool,
7730    near: NearInt,
7731    span: Span,
7732) -> Result<Array> {
7733    let dims = axis_counts(x, "reshape", near, span)?;
7734    if dims.iter().any(|&d| d < 0) {
7735        return Err(Error::domain("reshape lengths must be nonnegative", span));
7736    }
7737    let mut shape: Vec<usize> = dims.iter().map(|&d| d as usize).collect();
7738    // An item of a scalar is the scalar itself, and a scalar has one item.
7739    let (unit, src) = if by_items {
7740        let item_shape = if y.rank() == 0 { &[][..] } else { &y.shape[1..] };
7741        shape.extend_from_slice(item_shape);
7742        (item_shape.iter().product::<usize>(), y.items().max(usize::from(y.rank() == 0)))
7743    } else {
7744        (1, y.count())
7745    };
7746    let n = crate::limits::elements(&shape, span)?;
7747    let mut data = Data::empty(y.dtype());
7748    if n > 0 && src == 0 {
7749        if by_items {
7750            return Err(Error::new(ErrorKind::Length, "reshape of an empty array", Some(span)));
7751        }
7752        let fill = if apl { prototype_of(y) } else { None };
7753        let mut data = Data::empty(y.dtype());
7754        for _ in 0..n {
7755            push_gap(&mut data, &fill);
7756        }
7757        return Ok(Array::new(shape, data));
7758    }
7759    // Element i of the result is element `i % unit` of item
7760    // `(i / unit) % src`; with `unit` 1 that is the plain cyclic ravel.
7761    // Below `unit * src` the item index never wraps and that element is
7762    // element i itself, so a result the argument's own elements cover is a
7763    // change of shape and nothing else: the buffer comes through shared.
7764    if y.is_row_major() && n <= unit.saturating_mul(src) && n <= y.data.len() {
7765        return Ok(keep_proto(Array::new(shape, y.data.slice(0, n)), y, apl));
7766    }
7767    for i in 0..n {
7768        push_elem(&mut data, &y.data, (i / unit) % src * unit + i % unit);
7769    }
7770    Ok(keep_proto(Array::new(shape, data), y, apl))
7771}
7772
7773/// A take or drop that only touches the leading axis moves a run of whole
7774/// items, which is a slice of the buffer rather than an element-by-element
7775/// walk. `keep` is the items to end up with, `from` the first of them.
7776fn leading_run(y: &Array, counts: &[i64], drop: bool) -> Option<Array> {
7777    if y.rank() == 0 || counts.is_empty() {
7778        return None;
7779    }
7780    // The fast path holds only while every count after the first leaves its
7781    // axis alone. A drop of nothing is a zero; a take of everything is the
7782    // axis's own length, since a take of zero empties the axis instead.
7783    let trailing_untouched = counts[1..].iter().enumerate().all(|(a, &c)| {
7784        if drop { c == 0 } else { c.unsigned_abs() as usize == y.shape[a + 1] }
7785    });
7786    if !trailing_untouched {
7787        return None;
7788    }
7789    let n = y.items();
7790    let k = counts[0];
7791    let a = k.unsigned_abs() as usize;
7792    let (lo, keep) = if drop {
7793        let a = a.min(n);
7794        if k >= 0 { (a, n - a) } else { (0, n - a) }
7795    } else {
7796        // An overtake has to produce fills, which is not a slice.
7797        if a > n {
7798            return None;
7799        }
7800        if k >= 0 { (0, a) } else { (n - a, a) }
7801    };
7802    Some(section(y, lo, lo + keep))
7803}
7804
7805/// A count list the argument's rank cannot take. APL wants exactly one
7806/// count per axis; J takes fewer and leaves the rest of the axes whole, but
7807/// neither language takes more, and only a SCALAR right argument stretches
7808/// to whatever rank the list asks for.
7809fn count_rank(verb: &str, counts: usize, rank: usize, span: Span) -> Error {
7810    Error::new(
7811        ErrorKind::Length,
7812        format!("{counts} {verb} counts for a rank-{rank} argument"),
7813        Some(span),
7814    )
7815}
7816
7817fn take(
7818    x: &Array,
7819    y: &Array,
7820    prototype_fill: bool,
7821    apl: bool,
7822    near: NearInt,
7823    span: Span,
7824) -> Result<Array> {
7825    let counts = axis_counts(x, "take", near, span)?;
7826    // APL overtakes a nested array with the PROTOTYPE of its first item —
7827    // that item's shape, with a zero for every number and a blank for every
7828    // character. J fills with the empty box instead.
7829    let fill = if prototype_fill { prototype_of(y) } else { None };
7830    let promoted;
7831    // A scalar right argument is treated as a one-item array of whatever
7832    // rank the count list asks for: `1 2 {. 5` is a 1 by 2 table.
7833    let base = if y.rank() == 0 {
7834        promoted = Array::new(vec![1; counts.len()], y.data.clone());
7835        &promoted
7836    } else {
7837        y
7838    };
7839    // J's take, unlike its drop, wants at least one count.
7840    let wrong = if apl {
7841        counts.len() != base.rank()
7842    } else {
7843        counts.len() > base.rank() || (counts.is_empty() && base.rank() > 0)
7844    };
7845    if wrong {
7846        return Err(count_rank("take", counts.len(), base.rank(), span));
7847    }
7848    if let Some(run) = leading_run(base, &counts, false) {
7849        return Ok(keep_proto(run, base, prototype_fill));
7850    }
7851    let mut out_shape = base.shape.clone();
7852    for (a, &k) in counts.iter().enumerate() {
7853        out_shape[a] = k.unsigned_abs() as usize;
7854    }
7855    let n = crate::limits::elements(&out_shape, span)?;
7856    let st = strides(&base.shape);
7857    let mut data = Data::empty(base.dtype());
7858    let mut coord = vec![0usize; out_shape.len()];
7859    for _ in 0..n {
7860        let mut idx = 0usize;
7861        let mut inside = true;
7862        for a in 0..out_shape.len() {
7863            let len = base.shape[a] as i64;
7864            let c = coord[a] as i64;
7865            // Positive takes from the front and overtakes at the back;
7866            // negative takes from the back and overtakes at the front.
7867            let s = match counts.get(a) {
7868                Some(&k) if k < 0 => c + len - k.unsigned_abs() as i64,
7869                _ => c,
7870            };
7871            if s < 0 || s >= len {
7872                inside = false;
7873                break;
7874            }
7875            idx += s as usize * st[a];
7876        }
7877        if inside {
7878            push_elem(&mut data, &base.data, idx);
7879        } else if let (Data::Box(v), Some(p)) = (&mut data, &fill) {
7880            v.push(p.clone());
7881        } else {
7882            data.push_fill();
7883        }
7884        odometer(&mut coord, &out_shape);
7885    }
7886    Ok(keep_proto(Array::new(out_shape, data), base, prototype_fill))
7887}
7888
7889/// APL's prototype of a nested array: the first item's own shape, with a
7890/// zero where it holds a number and a blank where it holds a character,
7891/// and the same done to each of its items where it is nested itself.
7892fn prototype_of(y: &Array) -> Option<Array> {
7893    fn zeroed(a: &Array) -> Array {
7894        if let Some(items) = a.as_boxes() {
7895            let inner: Vec<Array> = items.iter().map(zeroed).collect();
7896            return Array::new(a.shape.clone(), Data::Box(inner.into()));
7897        }
7898        let dtype = match a.dtype() {
7899            DType::Char | DType::Symbol => a.dtype(),
7900            _ => DType::I64,
7901        };
7902        Array::new(a.shape.clone(), fill_data(dtype, a.count()))
7903    }
7904    match y.as_boxes()?.first() {
7905        Some(first) => Some(zeroed(first)),
7906        // No item to take one from: an empty nested array remembers what
7907        // its items looked like, and that is already a prototype.
7908        None => y.proto().cloned(),
7909    }
7910}
7911
7912/// An empty nested result remembers the prototype of the array it was made
7913/// from, so that a later fill, reshape or `↑` can answer with it rather
7914/// than with a bare empty box. A simple array's type already says what its
7915/// fills are, and J fills a nested one with the empty box whatever it held,
7916/// so only APL sets this.
7917fn keep_proto(out: Array, src: &Array, apl: bool) -> Array {
7918    if !apl || out.count() > 0 || out.dtype() != DType::Box {
7919        return out;
7920    }
7921    match prototype_of(src) {
7922        Some(p) => out.with_proto(p),
7923        None => out,
7924    }
7925}
7926
7927/// Write one element of fill: the prototype where the caller worked one out
7928/// and the array is nested, and the type's own fill otherwise.
7929fn push_gap(data: &mut Data, fill: &Option<Array>) {
7930    match (data, fill) {
7931        (Data::Box(v), Some(p)) => v.push(p.clone()),
7932        (d, _) => d.push_fill(),
7933    }
7934}
7935
7936fn drop_(x: &Array, y: &Array, apl: bool, near: NearInt, span: Span) -> Result<Array> {
7937    let counts = axis_counts(x, "drop", near, span)?;
7938    let promoted;
7939    let base = if y.rank() == 0 {
7940        promoted = Array::new(vec![1; counts.len()], y.data.clone());
7941        &promoted
7942    } else {
7943        y
7944    };
7945    let wrong =
7946        if apl { counts.len() != base.rank() } else { counts.len() > base.rank() };
7947    if wrong {
7948        return Err(count_rank("drop", counts.len(), base.rank(), span));
7949    }
7950    if let Some(run) = leading_run(base, &counts, true) {
7951        return Ok(keep_proto(run, base, apl));
7952    }
7953    let mut out_shape = base.shape.clone();
7954    let mut offset = vec![0usize; base.rank()];
7955    for (a, &k) in counts.iter().enumerate() {
7956        let len = base.shape[a];
7957        let d = (k.unsigned_abs() as usize).min(len);
7958        out_shape[a] = len - d;
7959        if k > 0 {
7960            offset[a] = d;
7961        }
7962    }
7963    let n: usize = out_shape.iter().product();
7964    let st = strides(&base.shape);
7965    let mut data = Data::empty(base.dtype());
7966    let mut coord = vec![0usize; out_shape.len()];
7967    for _ in 0..n {
7968        let idx: usize = (0..out_shape.len()).map(|a| (coord[a] + offset[a]) * st[a]).sum();
7969        push_elem(&mut data, &base.data, idx);
7970        odometer(&mut coord, &out_shape);
7971    }
7972    Ok(keep_proto(Array::new(out_shape, data), base, apl))
7973}
7974
7975/// Dyadic meaning of a primitive, applied to one pair of cells.
7976fn dyad_op(p: &Prim, x: &Array, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
7977    let apl = cfg.rules.lang == crate::Lang::Apl;
7978    let out = dyad_op_inner(p, x, y, cfg, span);
7979    if apl { out.map(tightened_mixed) } else { out }
7980}
7981
7982/// Every APL result passes through [`tightened_mixed`] on the way out, so
7983/// the mixed simple form never outlives the mixture that called for it.
7984fn dyad_op_inner(p: &Prim, x: &Array, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
7985    let tol = cfg.tol;
7986    let apl = cfg.rules.lang == crate::Lang::Apl;
7987    match p.dyad {
7988        // Reached only when a scalar verb is given non-zero cell ranks; the
7989        // cells then agree among themselves.
7990        DyadOp::Scalar(op) => scalar_dyad(op, x, y, cfg, span),
7991        DyadOp::Reshape => {
7992            let apl = cfg.rules.lang == crate::Lang::Apl;
7993            reshape(x, y, cfg.agreement == Agreement::LeadingPrefix, apl, cfg.near(), span)
7994        }
7995        DyadOp::Take => {
7996            let apl = cfg.rules.lang == crate::Lang::Apl;
7997            take(x, y, cfg.agreement == Agreement::ExactOrScalar, apl, cfg.near(), span)
7998        }
7999        DyadOp::Drop => drop_(x, y, cfg.rules.lang == crate::Lang::Apl, cfg.near(), span),
8000        DyadOp::Right => Ok(y.clone()),
8001        DyadOp::Left => Ok(x.clone()),
8002        DyadOp::Rotate => rotate(x, y, cfg.near(), span),
8003        DyadOp::RotateApl { last } => rotate_apl(x, y, last, cfg.near(), span),
8004        // Only J fills a ragged catenation; APL's conformability rule
8005        // refuses it, as the reference does.
8006        DyadOp::AppendLeading => {
8007            catenate(x, y, true, cfg.agreement == Agreement::LeadingPrefix, span)
8008        }
8009        DyadOp::AppendLast => {
8010            catenate(x, y, false, cfg.agreement == Agreement::LeadingPrefix, span)
8011        }
8012        DyadOp::IndexOf { origin, vector_left } => {
8013            let (x, y) = align_mixed(x, y, apl);
8014            index_of(&x, &y, origin, vector_left, tol, span)
8015        }
8016        DyadOp::MemberJ => Ok(member_j(x, y, tol)),
8017        DyadOp::MemberApl => {
8018            let (x, y) = align_mixed(x, y, apl);
8019            Ok(member_apl(&x, &y, tol))
8020        }
8021        DyadOp::From => from_index(x, y, cfg.near(), span),
8022        DyadOp::Match => {
8023            // APL tells an empty CHARACTER array from an empty numeric one
8024            // — their prototypes differ — where J's `-:` reads only the
8025            // shape once there is nothing left to compare.
8026            let empties_differ = cfg.rules.lang == crate::Lang::Apl
8027                && x.count() == 0
8028                && y.count() == 0
8029                && (x.dtype() == DType::Char) != (y.dtype() == DType::Char);
8030            Ok(Array::scalar_bool(!empties_differ && arrays_match(x, y, tol)))
8031        }
8032        DyadOp::NotMatch => Ok(Array::scalar_bool(!arrays_match(x, y, tol))),
8033        DyadOp::GradeSelect { down } => grade_select(x, y, down, cfg.rules, cfg.tol, span),
8034        DyadOp::Copy => {
8035            copy_items(x, y, cfg.agreement == Agreement::ExactOrScalar, cfg.near(), span)
8036        }
8037        DyadOp::CollateGrade { down, origin } => collate_grade(x, y, down, origin, span),
8038        DyadOp::TransposeJ => transpose_j(x, y, cfg.near(), span),
8039        DyadOp::TransposeApl => transpose_apl(x, y, cfg.rules.origin, cfg.near(), span),
8040        DyadOp::DecodeApl => decode_apl(x, y, span).map(|r| carry_exact2(r, x, y)),
8041        DyadOp::EncodeApl => {
8042            // Dyalog takes the digits exactly, so the tolerance the rest of
8043            // the sentence runs under is set aside for this one reading.
8044            let tol = match cfg.rules.encode_digits {
8045                EncodeDigits::Tolerant => cfg.tol,
8046                EncodeDigits::Exact => Tol { ct: 0.0, ..cfg.tol },
8047            };
8048            encode_apl(x, y, tol, span).map(|r| carry_exact2(r, x, y))
8049        }
8050        DyadOp::Decode => decode(Some(x), y, cfg.tol, span).map(|r| carry_exact2(r, x, y)),
8051        DyadOp::Encode => encode(x, y, cfg.tol, span).map(|r| carry_exact2(r, x, y)),
8052        DyadOp::Laminate => laminate(x, y, span),
8053        DyadOp::Link => link(x, y, span),
8054        DyadOp::Strand => strand(x, y, span),
8055        DyadOp::IntervalIndex { offset, closed } => {
8056            interval_index(x, y, offset, closed, tol, Grading::of(cfg.rules, tol), span)
8057        }
8058        DyadOp::IndexOfLast { origin } => Ok(index_of_last(x, y, origin, tol)),
8059        DyadOp::MatrixDivide => matrix_divide(x, y, span),
8060        DyadOp::PartitionEnclose => partition_enclose(x, y, cfg.near(), span),
8061        DyadOp::PartitionCounts => partition_counts(x, y, cfg.near(), span),
8062        DyadOp::Squad { origin, leading } => squad(x, y, origin, leading, cfg.near(), span),
8063        DyadOp::SelectAxis { axis, rank, origin } => {
8064            select_axis(x, y, axis, rank, origin, cfg.near(), span)
8065        }
8066        DyadOp::Fetch => fetch(x, y, cfg.near(), span),
8067        DyadOp::PolyEval => poly_eval(x, y, span),
8068        DyadOp::PolyIntegral => poly_integral(x, y, span),
8069        DyadOp::TruthTable(m) => truth_table(m, x, y, span),
8070        DyadOp::FormatSpec => format_spec(x, y, &cfg.fmt, span),
8071        DyadOp::FormatSpecJ => format_spec_j(x, y, &cfg.fmt, span),
8072        DyadOp::ParseNumbers => parse_numbers(x, y, span),
8073        DyadOp::SequentialMachine => sequential_machine(x, y, span),
8074        DyadOp::Deal { origin, fixed } => deal(x, y, origin, fixed, cfg.near(), span),
8075        DyadOp::ExactForm => exact_form(x, y, cfg.near(), span),
8076        DyadOp::Boolean(op) => bool_dyad(op, x, y, cfg, span),
8077        DyadOp::Less => {
8078            set_rank(cfg, "without", x, y, span)?;
8079            let (x, y) = align_mixed(x, y, apl);
8080            Ok(set_less(&x, &y, tol))
8081        }
8082        DyadOp::Union => {
8083            set_rank(cfg, "union", x, y, span)?;
8084            let (x, y) = align_mixed(x, y, apl);
8085            union_items(&x, &y, tol, span)
8086        }
8087        DyadOp::Intersect => {
8088            set_rank(cfg, "intersection", x, y, span)?;
8089            let (x, y) = align_mixed(x, y, apl);
8090            Ok(intersect_items(&x, &y, tol))
8091        }
8092        DyadOp::AnagramFrom => anagram_from(x, y, cfg.near(), span),
8093        DyadOp::Permute => permute(x, y, cfg.near(), span),
8094        DyadOp::FindSeq => {
8095            let (x, y) = align_mixed(x, y, apl);
8096            find_seq(&x, &y, tol, apl, span)
8097        }
8098        DyadOp::UnicodeForm => unicode_form(x, y, cfg.near(), span),
8099        DyadOp::SymbolForm => symbol_form(x, y, span),
8100        DyadOp::SparseForm => sparse_form(x, y, cfg.near(), span),
8101        DyadOp::PrimeMeta => prime_meta(x, y, cfg.near(), span).map(|r| carry_exact2(r, x, y)),
8102        DyadOp::PrimeExponents => {
8103            prime_exponents(x, y, cfg.near(), span).map(|r| carry_exact2(r, x, y))
8104        }
8105        DyadOp::Pick { origin } => pick(x, y, origin, cfg.near(), span),
8106        DyadOp::Expand => expand(x, y, cfg.rules.lang == crate::Lang::Apl, cfg.near(), span),
8107        // Writing needs the output sink, which this dispatcher does not
8108        // carry; `dyad_cell` takes it before the call gets here.
8109        DyadOp::WriteStream => Err(Error::internal("1!:2 reached the pure dyad dispatcher")),
8110        DyadOp::NotYet(what) => Err(Error::not_yet(what, span)),
8111        DyadOp::None => Err(Error::domain(format!("{} has no dyadic meaning", p.name), span)),
8112    }
8113}
8114
8115// ------------------------------------------------------------- reduction
8116
8117/// The extreme APL reduces an empty `⌈` or `⌊` to.
8118///
8119/// The language has no infinity in its identities, and the reference does
8120/// not answer the exact largest double either: `⌈/⍬` is this number to
8121/// every digit GNU APL will show of it, and arithmetic on the answer
8122/// confirms the rest. J's identities are the infinities and stay so.
8123const APL_EXTREME: f64 = 1.7976e308;
8124
8125/// The neutral cell of a reduction over no items, if the verb has one.
8126///
8127/// The values are the ones the references produce — both of them, for every
8128/// verb both spell (`x %: y` is J's alone). Where a table entry is
8129/// conventional rather than algebraic (a comparison has no true identity)
8130/// J and GNU APL still agree on it, so libjay follows. `⌊` and `⌈` are the
8131/// one place the two references part: J's neutral cells are the infinities
8132/// and APL's are the extremes of the representable range, so the table
8133/// reads the language.
8134fn reduce_identity(v: &Verb, n: usize, lang: crate::Lang) -> Option<Data> {
8135    let Verb::Prim(p) = v else { return None };
8136    let DyadOp::Scalar(op) = p.dyad else { return None };
8137    let ints = |k: i64| Data::I64(vec![k; n].into());
8138    let bits = |k: u8| Data::Bool(vec![k; n].into());
8139    let extreme =
8140        |sign: f64| Data::F64(vec![sign * if lang == crate::Lang::Apl { APL_EXTREME } else { f64::INFINITY }; n].into());
8141    Some(match op {
8142        ScalarDyad::Add | ScalarDyad::Sub | ScalarDyad::Gcd | ScalarDyad::Residue => ints(0),
8143        ScalarDyad::Mul
8144        | ScalarDyad::DivJ
8145        | ScalarDyad::DivApl
8146        | ScalarDyad::Pow
8147        | ScalarDyad::Lcm
8148        | ScalarDyad::Root
8149        | ScalarDyad::Binomial => ints(1),
8150        ScalarDyad::Min => extreme(1.0),
8151        ScalarDyad::Max => extreme(-1.0),
8152        ScalarDyad::Eq | ScalarDyad::Le | ScalarDyad::Ge => bits(1),
8153        ScalarDyad::Ne | ScalarDyad::Lt | ScalarDyad::Gt => bits(0),
8154        // `j.` and `r.` build a complex number out of two reals; neither
8155        // reference gives them an identity element.
8156        ScalarDyad::MakeComplex | ScalarDyad::PolarBy => return None,
8157        // Logarithm and the circle functions have none: both references
8158        // refuse an empty reduction of them.
8159        ScalarDyad::Log | ScalarDyad::Circle => return None,
8160    })
8161}
8162
8163/// Of the operations the typed fold covers, the ones whose reduction may be
8164/// regrouped: folding the items in chunks and combining the chunks gives the
8165/// same result, exactly for integers and to within the tolerance the float
8166/// contract allows (§5.9). LCM and GCD associate too but reduce through the
8167/// general path, which carries their type rules.
8168fn is_associative(op: ScalarDyad) -> bool {
8169    use ScalarDyad::*;
8170    matches!(op, Add | Mul | Min | Max)
8171}
8172
8173#[inline(always)]
8174fn fold_range_body<S, T, F>(
8175    v: &[S],
8176    m: usize,
8177    lo: usize,
8178    hi: usize,
8179    j0: usize,
8180    acc: &mut [T],
8181    step: &F,
8182) -> bool
8183where
8184    S: Widen<T>,
8185    T: Copy,
8186    F: Fn(T, T) -> (T, bool),
8187{
8188    let w = acc.len();
8189    let base = (hi - 1) * m + j0;
8190    for (slot, &x) in acc.iter_mut().zip(&v[base..base + w]) {
8191        *slot = x.widen();
8192    }
8193    // Overflow is folded into a flag rather than breaking the loop: the
8194    // whole reduction is redone by the general path either way.
8195    let mut over = false;
8196    for i in (lo..hi - 1).rev() {
8197        let row = &v[i * m + j0..i * m + j0 + w];
8198        for (slot, &x) in acc.iter_mut().zip(row) {
8199            let (r, o) = step(x.widen(), *slot);
8200            *slot = r;
8201            over |= o;
8202        }
8203    }
8204    !over
8205}
8206
8207multiversioned! {
8208    #[allow(clippy::too_many_arguments)]
8209    fn fold_range_vectorised[S: Widen<T>, T: Copy, F: Fn(T, T) -> (T, bool)](
8210        v: &[S],
8211        m: usize,
8212        lo: usize,
8213        hi: usize,
8214        j0: usize,
8215        acc: &mut [T],
8216        step: &F,
8217    ) -> bool = fold_range_body;
8218}
8219
8220/// Columns per fold below which the baseline compilation wins.
8221///
8222/// The only loop a wider vector can widen here is the one across an item's
8223/// columns, and a loop of a few columns spends more on entering the vector
8224/// body than the width gives back. Measured on `+/ m` over 20M f64 on one
8225/// thread: at 4 and 8 columns the AVX2 clone is about 1.5x slower than the
8226/// baseline one, at 16 columns and above it is 1.2x to 1.6x faster.
8227const VECTOR_COLUMNS: usize = 16;
8228
8229/// Fold items `lo .. hi` into `acc`, right to left, taking only the columns
8230/// that start at `j0` — `acc.len()` of them. False when a step left the
8231/// element type; the accumulator is then meaningless.
8232///
8233/// Wide enough, and this is the reduce that vectorises, so it runs the
8234/// compilation the CPU is entitled to; narrow, and it runs the baseline one.
8235/// Either way the fold order is the same: the columns are independent
8236/// accumulators, not a reassociation of one.
8237///
8238/// The buffer is read in its own element type and promoted into the
8239/// accumulator's where each element is read, so a narrower argument costs
8240/// no widened copy.
8241#[allow(clippy::too_many_arguments)]
8242#[inline]
8243fn fold_range<S, T, F>(
8244    v: &[S],
8245    m: usize,
8246    lo: usize,
8247    hi: usize,
8248    j0: usize,
8249    acc: &mut [T],
8250    step: &F,
8251) -> bool
8252where
8253    S: Widen<T>,
8254    T: Copy,
8255    F: Fn(T, T) -> (T, bool),
8256{
8257    if acc.len() < VECTOR_COLUMNS {
8258        fold_range_body(v, m, lo, hi, j0, acc, step)
8259    } else {
8260        fold_range_vectorised(v, m, lo, hi, j0, acc, step)
8261    }
8262}
8263
8264/// Independent accumulators an associative fold over a flat run keeps in
8265/// flight at once.
8266///
8267/// One accumulator makes the fold a chain of dependent steps — a float add
8268/// is four cycles on this class of machine, and nothing else can start
8269/// until it retires — so the loop waits on latency and leaves both the
8270/// pipeline and the vector registers idle. Lanes break the chain into
8271/// independent ones and give the autovectoriser a shape it can widen: lane
8272/// `j` takes every eighth element, which is a contiguous vector load.
8273/// Eight is two AVX2 registers of f64 and four of the complex pair.
8274const FOLD_LANES: usize = 8;
8275
8276/// Elements below which a flat fold keeps its plain single accumulator.
8277///
8278/// Below this the lanes cost more to set up and combine than the width
8279/// gives back, and a short fold keeps exactly the rounding it always had.
8280const MIN_LANE_WORK: usize = 8 * FOLD_LANES;
8281
8282/// Fold a flat run right to left with [`FOLD_LANES`] accumulators, the
8283/// lanes combined right to left at the end and the leading remainder folded
8284/// into the result last — so the fold is a regrouping of the sequential one,
8285/// which only an associative step may take (§5.9).
8286#[inline(always)]
8287fn fold_lanes_body<S, T, F>(v: &[S], step: &F) -> Option<T>
8288where
8289    S: Widen<T>,
8290    T: Copy,
8291    F: Fn(T, T) -> (T, bool),
8292{
8293    let n = v.len();
8294    let mut over = false;
8295    if n < MIN_LANE_WORK {
8296        let mut acc = v[n - 1].widen();
8297        for &x in v[..n - 1].iter().rev() {
8298            let (r, o) = step(x.widen(), acc);
8299            acc = r;
8300            over |= o;
8301        }
8302        return (!over).then_some(acc);
8303    }
8304    // The lanes cover a whole number of rows at the end of the run; `head`
8305    // is what is left over at the front.
8306    let rows = n / FOLD_LANES;
8307    let head = n - rows * FOLD_LANES;
8308    let last = head + (rows - 1) * FOLD_LANES;
8309    let mut acc = [v[last].widen(); FOLD_LANES];
8310    for (slot, &x) in acc.iter_mut().zip(&v[last..last + FOLD_LANES]) {
8311        *slot = x.widen();
8312    }
8313    for r in (0..rows - 1).rev() {
8314        let row = &v[head + r * FOLD_LANES..head + (r + 1) * FOLD_LANES];
8315        for (slot, &x) in acc.iter_mut().zip(row) {
8316            let (r, o) = step(x.widen(), *slot);
8317            *slot = r;
8318            over |= o;
8319        }
8320    }
8321    let mut a = acc[FOLD_LANES - 1];
8322    for &x in acc[..FOLD_LANES - 1].iter().rev() {
8323        let (r, o) = step(x, a);
8324        a = r;
8325        over |= o;
8326    }
8327    for &x in v[..head].iter().rev() {
8328        let (r, o) = step(x.widen(), a);
8329        a = r;
8330        over |= o;
8331    }
8332    (!over).then_some(a)
8333}
8334
8335multiversioned! {
8336    fn fold_lanes_vectorised[S: Widen<T>, T: Copy, F: Fn(T, T) -> (T, bool)](
8337        v: &[S],
8338        step: &F,
8339    ) -> Option<T> = fold_lanes_body;
8340}
8341
8342/// A flat run folded with lanes where they pay and with one accumulator
8343/// where they do not.
8344#[inline]
8345fn fold_lanes<S, T, F>(v: &[S], step: &F) -> Option<T>
8346where
8347    S: Widen<T>,
8348    T: Copy,
8349    F: Fn(T, T) -> (T, bool),
8350{
8351    if v.len() < MIN_LANE_WORK {
8352        fold_lanes_body(v, step)
8353    } else {
8354        fold_lanes_vectorised(v, step)
8355    }
8356}
8357
8358/// Fold `n` single-element items, right to left. Associative steps fold in
8359/// chunks on several threads, and in lanes within a chunk.
8360fn fold_flat<S, T, F>(v: &[S], n: usize, assoc: bool, step: &F) -> Option<T>
8361where
8362    S: Widen<T>,
8363    T: Copy + Send + Sync,
8364    F: Fn(T, T) -> (T, bool) + Sync + Send,
8365{
8366    if assoc {
8367        return par::try_fold_chunks(
8368            &v[..n],
8369            |part| fold_lanes(part, step),
8370            |a, b| {
8371                let (r, o) = step(a, b);
8372                (!o).then_some(r)
8373            },
8374        );
8375    }
8376    let mut acc = v[n - 1].widen();
8377    let mut over = false;
8378    for &x in v[..n - 1].iter().rev() {
8379        let (r, o) = step(x.widen(), acc);
8380        acc = r;
8381        over |= o;
8382    }
8383    (!over).then_some(acc)
8384}
8385
8386/// Fold the `n` items of a flat buffer into one item of `m` elements, right
8387/// to left. None when a step left the element type (integer overflow): the
8388/// caller then re-folds through the general path, which knows how to widen.
8389///
8390/// Three shapes, each yielding what one sequential pass would:
8391/// * a wide item splits into ranges of columns, and every element folds its
8392///   own column in order, so any step at all is safe;
8393/// * a one-element item folds in a register;
8394/// * a narrow item splits into chunks of items, which regroups the fold and
8395///   is taken only for an associative step.
8396fn fold_items<S, T, F>(v: &[S], n: usize, m: usize, assoc: bool, step: F) -> Option<Vec<T>>
8397where
8398    S: Widen<T>,
8399    T: Copy + Default + Send + Sync,
8400    F: Fn(T, T) -> (T, bool) + Sync + Send,
8401{
8402    if m >= par::WIDE_ITEM {
8403        let (out, ok) = par::fill_wide(m, n * m, |j0, acc: &mut [T]| {
8404            fold_range(v, m, 0, n, j0, acc, &step)
8405        });
8406        return ok.then_some(out);
8407    }
8408    if m == 1 {
8409        return fold_flat(v, n, assoc, &step).map(|x| vec![x]);
8410    }
8411    let chunks = if assoc { par::chunks(n, n * m) } else { 1 };
8412    if chunks < 2 {
8413        let mut acc = vec![T::default(); m];
8414        return fold_range(v, m, 0, n, 0, &mut acc, &step).then_some(acc);
8415    }
8416    let per = n.div_ceil(chunks);
8417    let parts = par::map_indexed(n.div_ceil(per), |c| {
8418        let mut acc = vec![T::default(); m];
8419        let ok = fold_range(v, m, c * per, ((c + 1) * per).min(n), 0, &mut acc, &step);
8420        ok.then_some(acc)
8421    });
8422    // The chunk results combine right to left, the order the chunks
8423    // themselves were folded in.
8424    let mut it = parts.into_iter().rev();
8425    let mut acc = it.next()??;
8426    for part in it {
8427        let part = part?;
8428        let mut over = false;
8429        for (slot, &x) in acc.iter_mut().zip(&part) {
8430            let (r, o) = step(x, *slot);
8431            *slot = r;
8432            over |= o;
8433        }
8434        if over {
8435            return None;
8436        }
8437    }
8438    Some(acc)
8439}
8440
8441/// One step of a blockwise float fold, scan or window.
8442///
8443/// A NaN abandons the block, exactly as an integer overflow does, and the
8444/// general path redoes the fold one pair at a time. That is where the
8445/// dialect's rules live — J's `*/ 0 , _` is 0 and its `+/ _ , __` is
8446/// refused, and each of those is an IEEE NaN — so the blockwise form never
8447/// has to carry them, and never answers differently from the plain one. An
8448/// infinity is an ordinary value and stays in the block. Ordinary data
8449/// takes this road once per fold and finds nothing.
8450#[inline(always)]
8451fn block_f64(r: f64) -> (f64, bool) {
8452    (r, r.is_nan())
8453}
8454
8455/// The integer fold, over any buffer whose elements are integers once read:
8456/// an `i64` one, or a boolean one promoted where it is read.
8457fn fold_i64<S: Widen<i64>>(op: ScalarDyad, v: &[S], n: usize, m: usize) -> Option<Vec<i64>> {
8458    use ScalarDyad::*;
8459    let assoc = is_associative(op);
8460    match op {
8461        Add => fold_items(v, n, m, assoc, i64::overflowing_add),
8462        Sub => fold_items(v, n, m, assoc, i64::overflowing_sub),
8463        Mul => fold_items(v, n, m, assoc, i64::overflowing_mul),
8464        Min => fold_items(v, n, m, assoc, |a: i64, b: i64| (a.min(b), false)),
8465        Max => fold_items(v, n, m, assoc, |a: i64, b: i64| (a.max(b), false)),
8466        _ => None,
8467    }
8468}
8469
8470fn fold_cx(op: ScalarDyad, v: &[Cx], n: usize, m: usize) -> Option<Vec<Cx>> {
8471    use ScalarDyad::*;
8472    let assoc = is_associative(op);
8473    match op {
8474        Add => fold_items(v, n, m, assoc, |a: Cx, b: Cx| (cx::add(a, b), false)),
8475        Sub => fold_items(v, n, m, assoc, |a: Cx, b: Cx| (cx::sub(a, b), false)),
8476        Mul => fold_items(v, n, m, assoc, |a: Cx, b: Cx| (cx::mul(a, b), false)),
8477        // Min and Max have no complex meaning; the general path reports it.
8478        _ => None,
8479    }
8480}
8481
8482fn fold_f64(op: ScalarDyad, v: &[f64], n: usize, m: usize) -> Option<Vec<f64>> {
8483    use ScalarDyad::*;
8484    let assoc = is_associative(op);
8485    match op {
8486        Add => fold_items(v, n, m, assoc, |a: f64, b: f64| block_f64(a + b)),
8487        Sub => fold_items(v, n, m, assoc, |a: f64, b: f64| block_f64(a - b)),
8488        Mul => fold_items(v, n, m, assoc, |a: f64, b: f64| block_f64(a * b)),
8489        Min => fold_items(v, n, m, assoc, |a: f64, b: f64| (a.min(b), false)),
8490        Max => fold_items(v, n, m, assoc, |a: f64, b: f64| (a.max(b), false)),
8491        _ => None,
8492    }
8493}
8494
8495/// Reduce a numeric buffer with one of the arithmetic operations, without
8496/// an intermediate array per step. None means this path does not apply and
8497/// the general fold must run.
8498fn reduce_typed(op: ScalarDyad, d: &Data, n: usize, m: usize) -> Option<Data> {
8499    use ScalarDyad::*;
8500    // The rest — comparisons, LCM/GCD, the float-only divisions — decide
8501    // their result type by rules the general path already carries.
8502    if !matches!(op, Add | Sub | Mul | Min | Max) {
8503        return None;
8504    }
8505    match d {
8506        Data::F64(v) => Some(Data::F64(fold_f64(op, v, n, m)?.into())),
8507        Data::Complex(v) => Some(Data::Complex(fold_cx(op, v, n, m)?.into())),
8508        Data::I64(v) => Some(Data::I64(fold_i64(op, v, n, m)?.into())),
8509        // Booleans reduce as integers, which is what promotion says the
8510        // general path would produce. The promotion happens where the fold
8511        // reads the element, so the boolean buffer is folded where it lies.
8512        Data::Bool(v) => Some(Data::I64(fold_i64(op, v.as_slice(), n, m)?.into())),
8513        // A bignum has no blockwise form: the exact types fold, scan and
8514        // window through the general path, one step at a time.
8515        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
8516    }
8517}
8518
8519/// Fold each run of `m` consecutive elements into one, right to left.
8520///
8521/// This is the reduction of a vector cell, done for every cell of the frame
8522/// at once. Each run is folded on its own, in the order the insert has, so
8523/// no step is regrouped and any operation at all is safe here.
8524#[inline(always)]
8525fn fold_runs_body<S, T, F>(v: &[S], start: usize, m: usize, out: &mut [T], step: &F) -> bool
8526where
8527    S: Widen<T>,
8528    T: Copy,
8529    F: Fn(T, T) -> (T, bool),
8530{
8531    let mut over = false;
8532    for (k, slot) in out.iter_mut().enumerate() {
8533        let run = &v[(start + k) * m..(start + k + 1) * m];
8534        let mut acc = run[m - 1].widen();
8535        for &x in run[..m - 1].iter().rev() {
8536            let (r, o) = step(x.widen(), acc);
8537            acc = r;
8538            over |= o;
8539        }
8540        *slot = acc;
8541    }
8542    !over
8543}
8544
8545multiversioned! {
8546    fn fold_runs_vectorised[S: Widen<T>, T: Copy, F: Fn(T, T) -> (T, bool)](
8547        v: &[S],
8548        start: usize,
8549        m: usize,
8550        out: &mut [T],
8551        step: &F,
8552    ) -> bool = fold_runs_body;
8553}
8554
8555/// One output per run of `m`, in parallel over the runs. None when a step
8556/// left the element type: the general path then runs and knows how to widen.
8557fn fold_runs<S, T, F>(v: &[S], n: usize, m: usize, step: F) -> Option<Vec<T>>
8558where
8559    S: Widen<T>,
8560    T: Copy + Default + Send + Sync,
8561    F: Fn(T, T) -> (T, bool) + Sync + Send,
8562{
8563    // A run is the loop a vector clone would widen, so a short run takes the
8564    // baseline compilation — the rule `VECTOR_COLUMNS` carries for the fold
8565    // across an item's columns, which is the same loop seen sideways.
8566    let wide = m >= VECTOR_COLUMNS;
8567    let (out, ok) = par::fill_wide(n, n * m, |start, part: &mut [T]| {
8568        if wide {
8569            fold_runs_vectorised(v, start, m, part, &step)
8570        } else {
8571            fold_runs_body(v, start, m, part, &step)
8572        }
8573    });
8574    ok.then_some(out)
8575}
8576
8577fn fold_runs_data(op: ScalarDyad, d: &Data, n: usize, m: usize) -> Option<Data> {
8578    use ScalarDyad::*;
8579    match d {
8580        Data::F64(v) => Some(Data::F64(
8581            match op {
8582                Add => fold_runs(v, n, m, |a: f64, b: f64| block_f64(a + b)),
8583                Sub => fold_runs(v, n, m, |a: f64, b: f64| block_f64(a - b)),
8584                Mul => fold_runs(v, n, m, |a: f64, b: f64| block_f64(a * b)),
8585                Min => fold_runs(v, n, m, |a: f64, b: f64| (a.min(b), false)),
8586                Max => fold_runs(v, n, m, |a: f64, b: f64| (a.max(b), false)),
8587                _ => None,
8588            }?
8589            .into(),
8590        )),
8591        Data::I64(v) => Some(Data::I64(fold_runs_i64(op, v.as_slice(), n, m)?.into())),
8592        // Min and Max have no complex meaning; the general path reports it.
8593        Data::Complex(v) => Some(Data::Complex(
8594            match op {
8595                Add => fold_runs(v, n, m, |a: Cx, b: Cx| (cx::add(a, b), false)),
8596                Sub => fold_runs(v, n, m, |a: Cx, b: Cx| (cx::sub(a, b), false)),
8597                Mul => fold_runs(v, n, m, |a: Cx, b: Cx| (cx::mul(a, b), false)),
8598                _ => None,
8599            }?
8600            .into(),
8601        )),
8602        // Booleans reduce as integers, and are promoted where they are read.
8603        Data::Bool(v) => Some(Data::I64(fold_runs_i64(op, v.as_slice(), n, m)?.into())),
8604        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
8605    }
8606}
8607
8608/// The row fold's integer arm, over an `i64` buffer or a boolean one.
8609fn fold_runs_i64<S: Widen<i64>>(op: ScalarDyad, v: &[S], n: usize, m: usize) -> Option<Vec<i64>> {
8610    use ScalarDyad::*;
8611    match op {
8612        Add => fold_runs(v, n, m, i64::overflowing_add),
8613        Sub => fold_runs(v, n, m, i64::overflowing_sub),
8614        Mul => fold_runs(v, n, m, i64::overflowing_mul),
8615        Min => fold_runs(v, n, m, |a: i64, b: i64| (a.min(b), false)),
8616        Max => fold_runs(v, n, m, |a: i64, b: i64| (a.max(b), false)),
8617        _ => None,
8618    }
8619}
8620
8621// ------------------------------------------------- folds over the columns
8622//
8623// A column-major buffer holds each column of the matrix contiguously, so
8624// the two reductions a table is asked for are both cheaper here than they
8625// are over rows: the leading-axis fold is one flat fold per column, and the
8626// row fold is one pass that reads the columns side by side. Neither
8627// regroups anything the row-major path does not already regroup, and
8628// neither materialises the transpose.
8629
8630/// The `runs` runs of `len` elements a buffer holds, as slices.
8631///
8632/// A buffer that arrived as parts — one per column of an imported table —
8633/// hands its parts back, so reading a table column by column never makes
8634/// the join and never copies. Any other buffer is cut into runs, which for
8635/// an owned or borrowed one is free as well.
8636fn run_slices<T: Clone>(b: &Buf<T>, runs: usize, len: usize) -> Vec<&[T]> {
8637    if let Some(parts) = b.parts() && parts.len() == runs && parts.iter().all(|p| p.len() == len) {
8638        return parts.iter().map(Buf::as_slice).collect();
8639    }
8640    let flat = b.as_slice();
8641    (0..runs).map(|c| &flat[c * len..(c + 1) * len]).collect()
8642}
8643
8644/// Fold each of `runs` contiguous runs of `len` elements into one value,
8645/// right to left.
8646///
8647/// A long run takes the flat fold, which keeps several accumulators in
8648/// flight and splits itself across threads; a short one is a run like any
8649/// other and takes the run fold, which parallelises across the runs
8650/// instead. Both fold in the insert's own order, up to the regrouping an
8651/// associative float fold is already allowed (§5.9).
8652fn fold_columns<S, T, F>(cols: &[&[S]], len: usize, assoc: bool, step: F) -> Option<Vec<T>>
8653where
8654    S: Widen<T>,
8655    T: Copy + Default + Send + Sync,
8656    F: Fn(T, T) -> (T, bool) + Sync + Send,
8657{
8658    // A column long enough to split takes the threads for itself, one
8659    // column at a time; a shorter one is folded whole and the split is
8660    // across the columns. Either way each column is folded by the flat
8661    // fold, which keeps its lanes and its contracted regrouping.
8662    if par::worth_it(len) {
8663        let mut out = Vec::with_capacity(cols.len());
8664        for c in cols {
8665            out.push(fold_flat(c, len, assoc, &step)?);
8666        }
8667        return Some(out);
8668    }
8669    let (out, ok) = par::fill_wide(cols.len(), cols.len() * len, |start, part: &mut [T]| {
8670        let mut ok = true;
8671        for (k, slot) in part.iter_mut().enumerate() {
8672            match fold_flat(cols[start + k], len, assoc, &step) {
8673                Some(v) => *slot = v,
8674                None => ok = false,
8675            }
8676        }
8677        ok
8678    });
8679    ok.then_some(out)
8680}
8681
8682/// Fold every column of a column-major buffer, one value per column.
8683fn fold_columns_data(op: ScalarDyad, d: &Data, runs: usize, len: usize) -> Option<Data> {
8684    use ScalarDyad::*;
8685    if !matches!(op, Add | Sub | Mul | Min | Max) {
8686        return None;
8687    }
8688    let assoc = is_associative(op);
8689    macro_rules! by {
8690        ($v:expr, $add:expr, $sub:expr, $mul:expr, $min:expr, $max:expr) => {{
8691            let cols = run_slices($v, runs, len);
8692            match op {
8693                Add => fold_columns(&cols, len, assoc, $add),
8694                Sub => fold_columns(&cols, len, assoc, $sub),
8695                Mul => fold_columns(&cols, len, assoc, $mul),
8696                Min => fold_columns(&cols, len, assoc, $min),
8697                Max => fold_columns(&cols, len, assoc, $max),
8698                _ => None,
8699            }?
8700        }};
8701    }
8702    match d {
8703        Data::F64(v) => Some(Data::F64(
8704            by!(
8705                v,
8706                |a: f64, b: f64| block_f64(a + b),
8707                |a: f64, b: f64| block_f64(a - b),
8708                |a: f64, b: f64| block_f64(a * b),
8709                |a: f64, b: f64| (a.min(b), false),
8710                |a: f64, b: f64| (a.max(b), false)
8711            )
8712            .into(),
8713        )),
8714        Data::I64(v) => Some(Data::I64(
8715            by!(
8716                v,
8717                i64::overflowing_add,
8718                i64::overflowing_sub,
8719                i64::overflowing_mul,
8720                |a: i64, b: i64| (a.min(b), false),
8721                |a: i64, b: i64| (a.max(b), false)
8722            )
8723            .into(),
8724        )),
8725        Data::Complex(v) => {
8726            if !matches!(op, Add | Sub | Mul) {
8727                return None;
8728            }
8729            Some(Data::Complex(
8730                by!(
8731                    v,
8732                    |a: Cx, b: Cx| (cx::add(a, b), false),
8733                    |a: Cx, b: Cx| (cx::sub(a, b), false),
8734                    |a: Cx, b: Cx| (cx::mul(a, b), false),
8735                    |_: Cx, _: Cx| unreachable!("refused above"),
8736                    |_: Cx, _: Cx| unreachable!("refused above")
8737                )
8738                .into(),
8739            ))
8740        }
8741        // Booleans reduce as integers, which is what promotion says the
8742        // general path would produce; the promotion happens where the fold
8743        // reads the element, so the columns are folded where they lie.
8744        Data::Bool(v) => Some(Data::I64(
8745            by!(
8746                v,
8747                i64::overflowing_add,
8748                i64::overflowing_sub,
8749                i64::overflowing_mul,
8750                |a: i64, b: i64| (a.min(b), false),
8751                |a: i64, b: i64| (a.max(b), false)
8752            )
8753            .into(),
8754        )),
8755        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
8756    }
8757}
8758
8759/// `u/ y` over a column-major argument: the leading axis is what each
8760/// contiguous run holds, so every run folds where it lies and no transpose
8761/// is made. None means the verb, the type or the shape is not one this
8762/// covers.
8763fn reduce_columns(v: &Verb, y: &Array) -> Option<Array> {
8764    let Verb::Prim(p) = v else { return None };
8765    let DyadOp::Scalar(op) = p.dyad else { return None };
8766    if !y.dtype().is_numeric() {
8767        return None;
8768    }
8769    let n = y.shape[0];
8770    let m: usize = y.shape[1..].iter().product();
8771    // An empty leading axis reduces to the operation's identity, which the
8772    // general path knows and this one does not.
8773    if n == 0 || m == 0 {
8774        return None;
8775    }
8776    let shape = y.shape[1..].to_vec();
8777    // One item reduces to that item, type and all: the insert never runs.
8778    // The trailing axes lie column-major, which is what the result keeps.
8779    if n == 1 {
8780        return Some(Array::col_major(shape, y.data.clone()));
8781    }
8782    let data = fold_columns_data(op, &y.data, m, n)?;
8783    Some(Array::col_major(shape, data))
8784}
8785
8786/// Fold the rows of a column-major matrix: one pass that reads the columns
8787/// side by side, each row folded right to left in the insert's own order.
8788fn fold_across<S, T, F>(cols: &[&[S]], rows: usize, step: F) -> Option<Vec<T>>
8789where
8790    S: Widen<T>,
8791    T: Copy + Default + Send + Sync,
8792    F: Fn(T, T) -> (T, bool) + Sync + Send,
8793{
8794    let (last, rest) = cols.split_last()?;
8795    let (out, ok) = par::fill(rows, |start, part: &mut [T]| {
8796        let mut over = false;
8797        for (k, slot) in part.iter_mut().enumerate() {
8798            let i = start + k;
8799            let mut acc = last[i].widen();
8800            for c in rest.iter().rev() {
8801                let (r, o) = step(c[i].widen(), acc);
8802                acc = r;
8803                over |= o;
8804            }
8805            *slot = acc;
8806        }
8807        !over
8808    });
8809    ok.then_some(out)
8810}
8811
8812fn fold_across_data(op: ScalarDyad, d: &Data, rows: usize, cols: usize) -> Option<Data> {
8813    use ScalarDyad::*;
8814    if !matches!(op, Add | Sub | Mul | Min | Max) {
8815        return None;
8816    }
8817    macro_rules! by {
8818        ($v:expr, $add:expr, $sub:expr, $mul:expr, $min:expr, $max:expr) => {{
8819            let parts = run_slices($v, cols, rows);
8820            match op {
8821                Add => fold_across(&parts, rows, $add),
8822                Sub => fold_across(&parts, rows, $sub),
8823                Mul => fold_across(&parts, rows, $mul),
8824                Min => fold_across(&parts, rows, $min),
8825                Max => fold_across(&parts, rows, $max),
8826                _ => None,
8827            }?
8828        }};
8829    }
8830    match d {
8831        Data::F64(v) => Some(Data::F64(
8832            by!(
8833                v,
8834                |a: f64, b: f64| block_f64(a + b),
8835                |a: f64, b: f64| block_f64(a - b),
8836                |a: f64, b: f64| block_f64(a * b),
8837                |a: f64, b: f64| (a.min(b), false),
8838                |a: f64, b: f64| (a.max(b), false)
8839            )
8840            .into(),
8841        )),
8842        Data::I64(v) => Some(Data::I64(
8843            by!(
8844                v,
8845                i64::overflowing_add,
8846                i64::overflowing_sub,
8847                i64::overflowing_mul,
8848                |a: i64, b: i64| (a.min(b), false),
8849                |a: i64, b: i64| (a.max(b), false)
8850            )
8851            .into(),
8852        )),
8853        Data::Complex(v) => {
8854            if !matches!(op, Add | Sub | Mul) {
8855                return None;
8856            }
8857            Some(Data::Complex(
8858                by!(
8859                    v,
8860                    |a: Cx, b: Cx| (cx::add(a, b), false),
8861                    |a: Cx, b: Cx| (cx::sub(a, b), false),
8862                    |a: Cx, b: Cx| (cx::mul(a, b), false),
8863                    |_: Cx, _: Cx| unreachable!("refused above"),
8864                    |_: Cx, _: Cx| unreachable!("refused above")
8865                )
8866                .into(),
8867            ))
8868        }
8869        // Read as integers where each element is read, as everywhere else.
8870        Data::Bool(v) => Some(Data::I64(
8871            by!(
8872                v,
8873                i64::overflowing_add,
8874                i64::overflowing_sub,
8875                i64::overflowing_mul,
8876                |a: i64, b: i64| (a.min(b), false),
8877                |a: i64, b: i64| (a.max(b), false)
8878            )
8879            .into(),
8880        )),
8881        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
8882    }
8883}
8884
8885/// `u/"1 y` over a column-major matrix: every row folded across the
8886/// columns, without the transpose the row-major path would need first.
8887fn reduce_rows_columns(u: &Verb, y: &Array) -> Option<Array> {
8888    let Verb::Reduce(inner) = u else { return None };
8889    let Verb::Prim(p) = &**inner else { return None };
8890    let DyadOp::Scalar(op) = p.dyad else { return None };
8891    // Only a matrix: at higher rank the cells this folds are not the runs
8892    // the buffer holds.
8893    if y.rank() != 2 || !y.dtype().is_numeric() {
8894        return None;
8895    }
8896    let (rows, cols) = (y.shape[0], y.shape[1]);
8897    // An empty cell reduces to the operation's identity, which the general
8898    // path knows and this one does not.
8899    if rows == 0 || cols == 0 {
8900        return None;
8901    }
8902    if cols == 1 {
8903        // A cell of one element reduces to that element, type and all.
8904        return Some(Array::new(vec![rows], y.data.clone()));
8905    }
8906    let data = fold_across_data(op, &y.data, rows, cols)?;
8907    Some(Array::new(vec![rows], data))
8908}
8909
8910/// `u/"1 y` and its like: a reduction whose cells are vectors, answered by
8911/// folding every cell out of the one buffer.
8912///
8913/// The rank machinery would build an array per cell, reduce it, and frame
8914/// the results — three allocations for every row of a matrix. This produces
8915/// exactly what that produces, and reads the buffer once. None means the
8916/// shape, the verb or the type is not one this covers, and the general path
8917/// runs instead.
8918fn reduce_vector_cells(u: &Verb, y: &Array, frame_rank: usize) -> Option<Array> {
8919    let Verb::Reduce(inner) = u else { return None };
8920    let Verb::Prim(p) = &**inner else { return None };
8921    let DyadOp::Scalar(op) = p.dyad else { return None };
8922    // The cell is a vector, so its reduction is a scalar and the result has
8923    // the frame's own shape.
8924    if y.rank() != frame_rank + 1 || !y.dtype().is_numeric() {
8925        return None;
8926    }
8927    let m = y.shape[frame_rank];
8928    // An empty cell reduces to the operation's identity, which the general
8929    // path knows and this one does not.
8930    if m == 0 {
8931        return None;
8932    }
8933    use ScalarDyad::{Add, Max, Min, Mul, Sub};
8934    if !matches!(op, Add | Sub | Mul | Min | Max) {
8935        return None;
8936    }
8937    let frame = y.shape[..frame_rank].to_vec();
8938    if m == 1 {
8939        // A cell of one element reduces to that element, type and all: the
8940        // insert never runs, so nothing widens.
8941        return Some(Array::new(frame, y.data.clone()));
8942    }
8943    let n: usize = frame.iter().product();
8944    let data = fold_runs_data(op, &y.data, n, m)?;
8945    Some(Array::new(frame, data))
8946}
8947
8948/// Insert `v` between the items of `y`, folding right to left.
8949fn reduce(v: &Verb, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
8950    if y.rank() == 0 {
8951        return Ok(y.clone());
8952    }
8953    let n = y.items();
8954    if n == 1 {
8955        return Ok(y.item(0));
8956    }
8957    let cell_shape = y.shape[1..].to_vec();
8958    let m: usize = cell_shape.iter().product();
8959    if n == 0 {
8960        // Catenation's identity is the empty LIST, whatever shape the cells
8961        // that were not there would have had: `,/ i. 0 3` is `i. 0`.
8962        if matches!(v, Verb::Prim(p) if matches!(p.dyad, DyadOp::AppendLeading | DyadOp::AppendLast))
8963        {
8964            return Ok(Array::new(vec![0], Data::empty(y.dtype())));
8965        }
8966        return match reduce_identity(v, m, ctx.cfg.rules.lang) {
8967            Some(d) => Ok(Array::new(cell_shape, d)),
8968            None => Err(Error::domain(
8969                format!("empty reduction has no identity for {}", v.name()),
8970                span,
8971            )),
8972        };
8973    }
8974    if y.dtype().is_numeric() && let Verb::Prim(p) = v && let DyadOp::Scalar(op) = p.dyad {
8975        // The typed fold covers the arithmetic reductions and runs
8976        // in parallel wherever the fold order allows; it declines
8977        // (integer overflow, an operation with its own type rules)
8978        // by returning None, and then the general fold below runs.
8979        if let Some(d) = reduce_typed(op, y.row_major_data(), n, m) {
8980            return Ok(Array::new(cell_shape, d));
8981        }
8982        // Fold over the raw buffer, one whole item per step, without
8983        // materialising item arrays.
8984        let mut acc = y.data.slice((n - 1) * m, n * m);
8985        for i in (0..n - 1).rev() {
8986            acc =
8987                scalar_dyad_data(
8988                    op,
8989                    &y.data,
8990                    i * m,
8991                    1,
8992                    &acc,
8993                    0,
8994                    1,
8995                    m,
8996                    ctx.cfg.tol,
8997                    ctx.cfg.rules,
8998                    span,
8999                )?;
9000        }
9001        return Ok(Array::new(cell_shape, acc));
9002    }
9003    if ctx.cfg.rules.lang == crate::Lang::Apl {
9004        return item_fold(v, y, ctx, span);
9005    }
9006    let mut acc = y.item(n - 1);
9007    for i in (0..n - 1).rev() {
9008        acc = v.dyad(&y.item(i), &acc, ctx, span)?;
9009    }
9010    Ok(acc)
9011}
9012
9013/// The same insert read by items, which is what APL's `f/` and `f⌿` are.
9014///
9015/// J folds whole cells: `,/ 2 3$i.6` catenates the two rows. APL folds the
9016/// ELEMENTS along the reduced axis and leaves the other axes as the frame,
9017/// so `,⌿2 3⍴⍳6` pairs the columns and answers three two-element vectors.
9018/// Each element is disclosed on the way in and the fold's value is enclosed
9019/// on the way out, which is why `,/1 2 3` is an enclosed vector rather than
9020/// a bare one. The arithmetic reductions never reach here: folding atoms
9021/// and folding cells agree for a scalar function, and the typed path above
9022/// keeps them.
9023fn item_fold(v: &Verb, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
9024    let n = y.items();
9025    let frame = y.shape[1..].to_vec();
9026    let m: usize = frame.iter().product();
9027    if m == 0 {
9028        return assemble(&frame, Vec::new(), span);
9029    }
9030    let base = y.to_row_major();
9031    let cells = each_cell(m, base.count(), v.is_pure(), ctx, |p, c| {
9032        let mut acc = open_cell(&atom(&base, (n - 1) * m + p));
9033        for i in (0..n - 1).rev() {
9034            acc = v.dyad(&open_cell(&atom(&base, i * m + p)), &acc, c, span)?;
9035        }
9036        Ok(enclose(&acc, Enclose::ExceptSimpleScalar))
9037    })?;
9038    assemble_items(&frame, cells, span)
9039}
9040
9041// ------------------------------------------------- windows, scans, power
9042
9043/// The elementwise operation a windowed verb folds with, when the verb is
9044/// exactly a reduction by a scalar primitive. The fast paths below apply
9045/// only then: they fold whole items at full rank, which is what `u/` does
9046/// and what any other spelling (a rank wrapper, a train) does not.
9047fn folded_op(u: &Verb) -> Option<ScalarDyad> {
9048    let Verb::Reduce(inner) = u else { return None };
9049    let Verb::Prim(p) = &**inner else { return None };
9050    match p.dyad {
9051        DyadOp::Scalar(op) => Some(op),
9052        _ => None,
9053    }
9054}
9055
9056/// Items `lo .. hi` of `y`, sharing its buffer where the buffer allows.
9057fn section(y: &Array, lo: usize, hi: usize) -> Array {
9058    let m = y.item_size();
9059    let mut shape = y.shape.clone();
9060    shape[0] = hi - lo;
9061    Array::new(shape, y.data.slice(lo * m, hi * m))
9062}
9063
9064/// `y` with a leading axis: a scalar is one item, which is how both
9065/// languages count the items of a rank-0 argument.
9066fn as_items(y: &Array) -> Option<Array> {
9067    (y.rank() == 0).then(|| Array::new(vec![1], y.data.clone()))
9068}
9069
9070#[inline(always)]
9071fn scan_flat_body<S, T, F>(v: &[S], n: usize, m: usize, back: bool, step: F) -> Option<Vec<T>>
9072where
9073    S: Widen<T>,
9074    T: Copy + Default,
9075    F: Fn(T, T) -> (T, bool),
9076{
9077    if m == 1 {
9078        // One element per item is the shape a time series has, and it is
9079        // the one worth keeping the accumulator in a register for.
9080        let mut out = vec![T::default(); n];
9081        let mut over = false;
9082        if back {
9083            let mut acc = v[n - 1].widen();
9084            out[n - 1] = acc;
9085            for (slot, &x) in out[..n - 1].iter_mut().zip(&v[..n - 1]).rev() {
9086                let (r, o) = step(x.widen(), acc);
9087                acc = r;
9088                over |= o;
9089                *slot = acc;
9090            }
9091        } else {
9092            let mut acc = v[0].widen();
9093            out[0] = acc;
9094            for (slot, &x) in out[1..n].iter_mut().zip(&v[1..n]) {
9095                let (r, o) = step(acc, x.widen());
9096                acc = r;
9097                over |= o;
9098                *slot = acc;
9099            }
9100        }
9101        return (!over).then_some(out);
9102    }
9103    let mut out = vec![T::default(); n * m];
9104    let mut acc = vec![T::default(); m];
9105    let mut over = false;
9106    if back {
9107        for (slot, &x) in acc.iter_mut().zip(&v[(n - 1) * m..n * m]) {
9108            *slot = x.widen();
9109        }
9110        out[(n - 1) * m..n * m].copy_from_slice(&acc);
9111        for i in (0..n - 1).rev() {
9112            for (j, slot) in acc.iter_mut().enumerate() {
9113                let (r, o) = step(v[i * m + j].widen(), *slot);
9114                *slot = r;
9115                over |= o;
9116            }
9117            out[i * m..i * m + m].copy_from_slice(&acc);
9118        }
9119    } else {
9120        for (slot, &x) in acc.iter_mut().zip(&v[..m]) {
9121            *slot = x.widen();
9122        }
9123        out[..m].copy_from_slice(&acc);
9124        for i in 1..n {
9125            for (j, slot) in acc.iter_mut().enumerate() {
9126                let (r, o) = step(*slot, v[i * m + j].widen());
9127                *slot = r;
9128                over |= o;
9129            }
9130            out[i * m..i * m + m].copy_from_slice(&acc);
9131        }
9132    }
9133    (!over).then_some(out)
9134}
9135
9136multiversioned! {
9137    fn scan_flat_vectorised[S: Widen<T>, T: Copy + Default, F: Fn(T, T) -> (T, bool)](
9138        v: &[S],
9139        n: usize,
9140        m: usize,
9141        back: bool,
9142        step: F,
9143    ) -> Option<Vec<T>> = scan_flat_body;
9144}
9145
9146/// Running fold over `n` items of `m` elements each, one output item per
9147/// step. Backward is exactly the insert's right-to-left order, so it holds
9148/// for any step; forward is the left-to-right order, which agrees with the
9149/// insert only when the step is associative. None when a step left the
9150/// element type.
9151///
9152/// Only the wide shape has anything to gain from a wider vector, and for
9153/// the same reason the reduce has: the loop that widens is the one across
9154/// an item's elements. A scan of one element per item is a chain of
9155/// dependent steps, which no vector shortens, so it takes the baseline
9156/// compilation.
9157fn scan_flat<S, T, F>(v: &[S], n: usize, m: usize, back: bool, step: F) -> Option<Vec<T>>
9158where
9159    S: Widen<T>,
9160    T: Copy + Default,
9161    F: Fn(T, T) -> (T, bool),
9162{
9163    if m < VECTOR_COLUMNS {
9164        scan_flat_body(v, n, m, back, step)
9165    } else {
9166        scan_flat_vectorised(v, n, m, back, step)
9167    }
9168}
9169
9170fn scan_i64<S: Widen<i64>>(
9171    op: ScalarDyad,
9172    v: &[S],
9173    n: usize,
9174    m: usize,
9175    back: bool,
9176) -> Option<Vec<i64>> {
9177    use ScalarDyad::*;
9178    match op {
9179        Add => scan_flat(v, n, m, back, i64::overflowing_add),
9180        Sub => scan_flat(v, n, m, back, i64::overflowing_sub),
9181        Mul => scan_flat(v, n, m, back, i64::overflowing_mul),
9182        Min => scan_flat(v, n, m, back, |a: i64, b: i64| (a.min(b), false)),
9183        Max => scan_flat(v, n, m, back, |a: i64, b: i64| (a.max(b), false)),
9184        _ => None,
9185    }
9186}
9187
9188fn scan_cx(op: ScalarDyad, v: &[Cx], n: usize, m: usize, back: bool) -> Option<Vec<Cx>> {
9189    use ScalarDyad::*;
9190    match op {
9191        Add => scan_flat(v, n, m, back, |a: Cx, b: Cx| (cx::add(a, b), false)),
9192        Sub => scan_flat(v, n, m, back, |a: Cx, b: Cx| (cx::sub(a, b), false)),
9193        Mul => scan_flat(v, n, m, back, |a: Cx, b: Cx| (cx::mul(a, b), false)),
9194        _ => None,
9195    }
9196}
9197
9198fn scan_f64<S: Widen<f64>>(
9199    op: ScalarDyad,
9200    v: &[S],
9201    n: usize,
9202    m: usize,
9203    back: bool,
9204) -> Option<Vec<f64>> {
9205    use ScalarDyad::*;
9206    match op {
9207        Add => scan_flat(v, n, m, back, |a: f64, b: f64| block_f64(a + b)),
9208        Sub => scan_flat(v, n, m, back, |a: f64, b: f64| block_f64(a - b)),
9209        Mul => scan_flat(v, n, m, back, |a: f64, b: f64| block_f64(a * b)),
9210        Min => scan_flat(v, n, m, back, |a: f64, b: f64| (a.min(b), false)),
9211        Max => scan_flat(v, n, m, back, |a: f64, b: f64| (a.max(b), false)),
9212        _ => None,
9213    }
9214}
9215
9216/// The scan of a numeric buffer in one pass. None means this path does not
9217/// apply. Integer overflow anywhere widens the whole result to float, which
9218/// is what the per-prefix reduction would also produce.
9219fn scan_typed(op: ScalarDyad, d: &Data, n: usize, m: usize, back: bool) -> Option<Data> {
9220    use ScalarDyad::*;
9221    if !matches!(op, Add | Sub | Mul | Min | Max) {
9222        return None;
9223    }
9224    // An integer buffer and a boolean one both scan as integers, each read
9225    // in its own type; the float retry reads the same buffer again rather
9226    // than a widened copy of it.
9227    fn ints<S: Widen<i64> + Widen<f64>>(
9228        op: ScalarDyad,
9229        v: &[S],
9230        n: usize,
9231        m: usize,
9232        back: bool,
9233    ) -> Data {
9234        match scan_i64(op, v, n, m, back) {
9235            Some(out) => Data::I64(out.into()),
9236            None => Data::F64(
9237                scan_f64(op, v, n, m, back).expect("the float scan cannot overflow").into(),
9238            ),
9239        }
9240    }
9241    match d {
9242        Data::F64(v) => Some(Data::F64(scan_f64(op, v.as_slice(), n, m, back)?.into())),
9243        Data::Complex(v) => Some(Data::Complex(scan_cx(op, v, n, m, back)?.into())),
9244        Data::I64(v) => Some(ints(op, v.as_slice(), n, m, back)),
9245        Data::Bool(v) => Some(ints(op, v.as_slice(), n, m, back)),
9246        // A bignum has no blockwise form: the exact types fold, scan and
9247        // window through the general path, one step at a time.
9248        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
9249    }
9250}
9251
9252/// The constant `c` of an affine step `x u y = x + c * y`, when the verb is
9253/// exactly that tree and `c` is a scalar noun written in the source.
9254///
9255/// The two spellings of a first-order recurrence are `[ + c * ]` and its
9256/// mirror `(c * ]) + [`. The match is on the tree, so a verb that computes
9257/// the same thing another way is not one of them and folds the general way.
9258fn affine_step(u: &Verb) -> Option<&Array> {
9259    // The ranks are part of the match: arithmetic pairs atoms and `[` and
9260    // `]` take whole arguments, and a verb wearing any other rank is a
9261    // different verb.
9262    fn prim(v: &Verb, want: DyadOp, ranks: [i64; 3]) -> bool {
9263        matches!(v, Verb::Prim(p) if p.dyad == want && p.ranks == ranks)
9264    }
9265    const ATOMS: [i64; 3] = [0, 0, 0];
9266    const WHOLE: [i64; 3] = [RANK_INF; 3];
9267    // `c * ]`: the accumulator scaled by the constant, and nothing else.
9268    fn scaled(v: &Verb) -> Option<&Array> {
9269        let Verb::NounFork(c, g, h) = v else { return None };
9270        let noun = c.rank() == 0
9271            && matches!(c.dtype(), DType::Bool | DType::I64 | DType::F64 | DType::Complex);
9272        let tree = prim(g, DyadOp::Scalar(ScalarDyad::Mul), ATOMS)
9273            && prim(h, DyadOp::Right, WHOLE);
9274        (noun && tree).then_some(c)
9275    }
9276    let Verb::Fork(f, g, h) = u else { return None };
9277    if !prim(g, DyadOp::Scalar(ScalarDyad::Add), ATOMS) {
9278        return None;
9279    }
9280    if prim(f, DyadOp::Left, WHOLE) {
9281        scaled(h)
9282    } else if prim(h, DyadOp::Left, WHOLE) {
9283        scaled(f)
9284    } else {
9285        None
9286    }
9287}
9288
9289/// The arithmetic a running affine fold needs of its element type, and the
9290/// test that a power of the constant is still a number.
9291struct Ring<T> {
9292    add: fn(T, T) -> T,
9293    mul: fn(T, T) -> T,
9294    one: T,
9295    finite: fn(T) -> bool,
9296}
9297
9298/// A running affine fold: `out[k] = v[k] + c * out[k+1]` backwards, and
9299/// forwards the same series carried the only way one pass can carry it —
9300/// the k-th prefix is the sum of `c^i * v[i]`, so the power of `c` runs
9301/// along with it. None when a power leaves the finite range, which is the
9302/// one case that sum and the fold it stands for do not agree on.
9303fn affine_flat<T>(v: &[T], c: T, n: usize, m: usize, back: bool, r: &Ring<T>) -> Option<Vec<T>>
9304where
9305    T: Copy + Default,
9306{
9307    let (add, mul) = (r.add, r.mul);
9308    let mut out = vec![T::default(); n * m];
9309    if back {
9310        out[(n - 1) * m..].copy_from_slice(&v[(n - 1) * m..n * m]);
9311        for i in (0..n - 1).rev() {
9312            for j in 0..m {
9313                out[i * m + j] = add(v[i * m + j], mul(c, out[(i + 1) * m + j]));
9314            }
9315        }
9316    } else {
9317        out[..m].copy_from_slice(&v[..m]);
9318        let mut pow = r.one;
9319        for i in 1..n {
9320            pow = mul(pow, c);
9321            if !(r.finite)(pow) {
9322                return None;
9323            }
9324            for j in 0..m {
9325                out[i * m + j] = add(out[(i - 1) * m + j], mul(pow, v[i * m + j]));
9326            }
9327        }
9328    }
9329    Some(out)
9330}
9331
9332/// `u/\ y` and `u/\. y` over an affine step, in one pass instead of one
9333/// fold per run.
9334///
9335/// Backwards this is the insert's own order — the steps are the steps the
9336/// general path takes, in the same order, so the answer is the same to the
9337/// last bit. Forwards it is the same series regrouped, which rounds as the
9338/// blocked window fold rounds and not as the insert would. None when the
9339/// types are not the ones that carry it: two integers fold exactly and are
9340/// left alone, as are the exact types.
9341fn affine_scan(c: &Array, y: &Array, back: bool) -> Option<Data> {
9342    let (n, m) = (y.items(), y.item_size());
9343    let machine = |t: DType| matches!(t, DType::Bool | DType::I64 | DType::F64 | DType::Complex);
9344    if n == 0 || !machine(c.dtype()) || !machine(y.dtype()) {
9345        return None;
9346    }
9347    match DType::promote(c.dtype(), y.dtype())? {
9348        DType::F64 => {
9349            let (mut tc, mut tv) = (Vec::new(), Vec::new());
9350            let k = *borrow_f64(&c.data, &mut tc).first()?;
9351            let v = borrow_f64(y.row_major_data(), &mut tv);
9352            let r = Ring { add: |a, b| a + b, mul: |a, b| a * b, one: 1.0, finite: f64::is_finite };
9353            Some(Data::F64(affine_flat(v, k, n, m, back, &r)?.into()))
9354        }
9355        DType::Complex => {
9356            let (mut tc, mut tv) = (Vec::new(), Vec::new());
9357            let k = *borrow_cx(&c.data, &mut tc).first()?;
9358            let v = borrow_cx(y.row_major_data(), &mut tv);
9359            let finite = |z: Cx| z[0].is_finite() && z[1].is_finite();
9360            let r = Ring { add: cx::add, mul: cx::mul, one: [1.0, 0.0], finite };
9361            Some(Data::Complex(affine_flat(v, k, n, m, back, &r)?.into()))
9362        }
9363        _ => None,
9364    }
9365}
9366
9367/// Fold every window of `w` consecutive items into one item.
9368///
9369/// The items are cut into blocks of `w`. Within a block the running folds
9370/// from its start and from its end are computed once each, and then every
9371/// window is either one whole block or one block's suffix combined with the
9372/// next block's prefix. That is two steps per element with no accumulator
9373/// running longer than `w` of them, so the float error of a window is the
9374/// error of computing that window on its own — a cumulative sum over the
9375/// whole argument, differenced, would instead carry the drift of the entire
9376/// series into every window.
9377///
9378/// `step` has to be associative: the grouping is not the insert's own. The
9379/// float reassociation is the §5.9 contract, the same one reduction takes.
9380/// None when a step left the element type.
9381fn window_fold<S, T, F>(v: &[S], n: usize, m: usize, w: usize, step: F) -> Option<Vec<T>>
9382where
9383    S: Widen<T>,
9384    T: Copy + Default + Send + Sync,
9385    F: Fn(T, T) -> (T, bool) + Sync + Send,
9386{
9387    debug_assert!(w >= 1 && n >= w);
9388    if m == 1 {
9389        return window_fold_flat(v, n, w, step);
9390    }
9391    let count = n - w + 1;
9392    let mut out = vec![T::default(); count * m];
9393    // Prefix folds of the current block, suffix folds of it and of the one
9394    // before: `w` items each, whatever the length of the argument.
9395    let mut pre = vec![T::default(); w * m];
9396    let mut suf = vec![T::default(); w * m];
9397    let mut prev = vec![T::default(); w * m];
9398    let mut over = false;
9399    for b in 0..n.div_ceil(w) {
9400        let bs = b * w;
9401        let be = ((b + 1) * w).min(n);
9402        for (slot, &x) in pre[..m].iter_mut().zip(&v[bs * m..bs * m + m]) {
9403            *slot = x.widen();
9404        }
9405        for i in 1..be - bs {
9406            let (o, p) = (i * m, (i - 1) * m);
9407            for j in 0..m {
9408                let (r, f) = step(pre[p + j], v[(bs + i) * m + j].widen());
9409                pre[o + j] = r;
9410                over |= f;
9411            }
9412        }
9413        // Every window whose last item is in this block; its first item is
9414        // either this block's start or somewhere in the block before.
9415        for e in bs.max(w - 1)..be {
9416            let i = e + 1 - w;
9417            let (oo, po) = (i * m, (e - bs) * m);
9418            if i == bs {
9419                out[oo..oo + m].copy_from_slice(&pre[po..po + m]);
9420            } else {
9421                let so = (i + w - bs) * m;
9422                for j in 0..m {
9423                    let (r, f) = step(prev[so + j], pre[po + j]);
9424                    out[oo + j] = r;
9425                    over |= f;
9426                }
9427            }
9428        }
9429        let last = be - 1 - bs;
9430        for (slot, &x) in suf[last * m..last * m + m]
9431            .iter_mut()
9432            .zip(&v[(be - 1) * m..be * m])
9433        {
9434            *slot = x.widen();
9435        }
9436        for i in (0..last).rev() {
9437            let (o, p) = (i * m, (i + 1) * m);
9438            for j in 0..m {
9439                let (r, f) = step(v[(bs + i) * m + j].widen(), suf[p + j]);
9440                suf[o + j] = r;
9441                over |= f;
9442            }
9443        }
9444        std::mem::swap(&mut prev, &mut suf);
9445    }
9446    (!over).then_some(out)
9447}
9448
9449/// [`window_fold`] for one element per item — a plain time series, and the
9450/// shape worth writing the loops out for: each of the three runs over a
9451/// block is a walk over one slice, so the accumulator stays in a register
9452/// and nothing is bounds-checked per element.
9453///
9454/// A range of the output depends only on the blocks its own windows lie in,
9455/// so the output splits across threads with nothing shared: a chunk starting
9456/// at `lo` starts at the block holding item `lo`, and the first window it
9457/// writes begins in that same block.
9458fn window_fold_flat<S, T, F>(v: &[S], n: usize, w: usize, step: F) -> Option<Vec<T>>
9459where
9460    S: Widen<T>,
9461    T: Copy + Default + Send + Sync,
9462    F: Fn(T, T) -> (T, bool) + Sync + Send,
9463{
9464    let (out, ok) = par::fill(n - w + 1, |lo, part: &mut [T]| {
9465        window_fold_range(v, n, w, lo, part, &step)
9466    });
9467    ok.then_some(out)
9468}
9469
9470#[inline(always)]
9471fn window_fold_range_body<S, T, F>(
9472    v: &[S],
9473    n: usize,
9474    w: usize,
9475    lo: usize,
9476    out: &mut [T],
9477    step: &F,
9478) -> bool
9479where
9480    S: Widen<T>,
9481    T: Copy + Default,
9482    F: Fn(T, T) -> (T, bool),
9483{
9484    if out.is_empty() {
9485        return true;
9486    }
9487    let hi = lo + out.len();
9488    let mut pre = vec![T::default(); w];
9489    let mut suf = vec![T::default(); w];
9490    let mut prev = vec![T::default(); w];
9491    let mut over = false;
9492    let mut bs = lo / w * w;
9493    // The last item any window of this chunk needs is `hi + w - 2`.
9494    while bs < n && bs <= hi + w - 2 {
9495        let block = &v[bs..(bs + w).min(n)];
9496        let lb = block.len();
9497        let mut acc = block[0].widen();
9498        pre[0] = acc;
9499        for (slot, &x) in pre[1..lb].iter_mut().zip(&block[1..]) {
9500            let (r, o) = step(acc, x.widen());
9501            acc = r;
9502            over |= o;
9503            *slot = acc;
9504        }
9505        // Every window of this chunk whose last item is in this block. Its
9506        // first item is this block's start, or is in the block before —
9507        // which is never the case in the first block a chunk touches, since
9508        // that block holds item `lo` and no window here starts earlier.
9509        for e in bs.max(lo + w - 1)..(bs + lb).min(hi + w - 1) {
9510            let i = e + 1 - w;
9511            out[i - lo] = if i == bs {
9512                pre[e - bs]
9513            } else {
9514                let (r, o) = step(prev[i + w - bs], pre[e - bs]);
9515                over |= o;
9516                r
9517            };
9518        }
9519        let mut acc = block[lb - 1].widen();
9520        suf[lb - 1] = acc;
9521        for (slot, &x) in suf[..lb - 1].iter_mut().zip(&block[..lb - 1]).rev() {
9522            let (r, o) = step(x.widen(), acc);
9523            acc = r;
9524            over |= o;
9525            *slot = acc;
9526        }
9527        std::mem::swap(&mut prev, &mut suf);
9528        bs += w;
9529    }
9530    !over
9531}
9532
9533multiversioned! {
9534    /// The windows `lo .. lo + out.len()`. False when a step left the type.
9535    /// Compiled per CPU feature level; the prefix and suffix passes it runs
9536    /// are dependent chains, so what a wider vector reaches here is the
9537    /// pairing of the two, not the passes themselves.
9538    fn window_fold_range[S: Widen<T>, T: Copy + Default, F: Fn(T, T) -> (T, bool)](
9539        v: &[S],
9540        n: usize,
9541        w: usize,
9542        lo: usize,
9543        out: &mut [T],
9544        step: &F,
9545    ) -> bool = window_fold_range_body;
9546}
9547
9548/// The windows of `w` items of `v` that begin at `lo` and after, folded into
9549/// `out` — one item per window, `out.len()` of them.
9550///
9551/// The fused kernel folds the windows of a block it computed itself, and
9552/// calls this to do it: the blocking is counted from `v`'s own start, so a
9553/// caller whose buffer starts on a multiple of `w` groups every window
9554/// exactly as the pass over the whole argument groups it. False when a step
9555/// left the element type.
9556pub(crate) fn windows_into<S, T, F>(v: &[S], w: usize, lo: usize, out: &mut [T], step: &F) -> bool
9557where
9558    S: Widen<T>,
9559    T: Copy + Default,
9560    F: Fn(T, T) -> (T, bool),
9561{
9562    window_fold_range(v, v.len(), w, lo, out, step)
9563}
9564
9565fn window_i64<S: Widen<i64>>(
9566    op: ScalarDyad,
9567    v: &[S],
9568    n: usize,
9569    m: usize,
9570    w: usize,
9571) -> Option<Vec<i64>> {
9572    use ScalarDyad::*;
9573    match op {
9574        Add => window_fold(v, n, m, w, i64::overflowing_add),
9575        Mul => window_fold(v, n, m, w, i64::overflowing_mul),
9576        Min => window_fold(v, n, m, w, |a: i64, b: i64| (a.min(b), false)),
9577        Max => window_fold(v, n, m, w, |a: i64, b: i64| (a.max(b), false)),
9578        _ => None,
9579    }
9580}
9581
9582fn window_cx(op: ScalarDyad, v: &[Cx], n: usize, m: usize, w: usize) -> Option<Vec<Cx>> {
9583    use ScalarDyad::*;
9584    match op {
9585        Add => window_fold(v, n, m, w, |a: Cx, b: Cx| (cx::add(a, b), false)),
9586        Mul => window_fold(v, n, m, w, |a: Cx, b: Cx| (cx::mul(a, b), false)),
9587        _ => None,
9588    }
9589}
9590
9591fn window_f64<S: Widen<f64>>(
9592    op: ScalarDyad,
9593    v: &[S],
9594    n: usize,
9595    m: usize,
9596    w: usize,
9597) -> Option<Vec<f64>> {
9598    use ScalarDyad::*;
9599    match op {
9600        Add => window_fold(v, n, m, w, |a: f64, b: f64| block_f64(a + b)),
9601        Mul => window_fold(v, n, m, w, |a: f64, b: f64| block_f64(a * b)),
9602        Min => window_fold(v, n, m, w, |a: f64, b: f64| (a.min(b), false)),
9603        Max => window_fold(v, n, m, w, |a: f64, b: f64| (a.max(b), false)),
9604        _ => None,
9605    }
9606}
9607
9608/// Moving windows over a numeric buffer in two passes. None means this path
9609/// does not apply: only the associative arithmetic can be regrouped into
9610/// blocks, so subtraction and every non-scalar verb go the general way.
9611fn window_typed(op: ScalarDyad, d: &Data, n: usize, m: usize, w: usize) -> Option<Data> {
9612    use ScalarDyad::*;
9613    if !matches!(op, Add | Mul | Min | Max) {
9614        return None;
9615    }
9616    // As in the scan: integers and booleans window as integers, each read in
9617    // its own type, and the float retry rereads the same buffer.
9618    fn ints<S: Widen<i64> + Widen<f64>>(
9619        op: ScalarDyad,
9620        v: &[S],
9621        n: usize,
9622        m: usize,
9623        w: usize,
9624    ) -> Data {
9625        match window_i64(op, v, n, m, w) {
9626            Some(out) => Data::I64(out.into()),
9627            None => {
9628                Data::F64(window_f64(op, v, n, m, w).expect("the float fold cannot overflow").into())
9629            }
9630        }
9631    }
9632    match d {
9633        Data::F64(v) => Some(Data::F64(window_f64(op, v.as_slice(), n, m, w)?.into())),
9634        Data::Complex(v) => Some(Data::Complex(window_cx(op, v, n, m, w)?.into())),
9635        Data::I64(v) => Some(ints(op, v.as_slice(), n, m, w)),
9636        Data::Bool(v) => Some(ints(op, v.as_slice(), n, m, w)),
9637        // A bignum has no blockwise form: the exact types fold, scan and
9638        // window through the general path, one step at a time.
9639        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
9640    }
9641}
9642
9643/// `u\ y` and `u\. y`: the verb applied to every prefix, or to every suffix.
9644fn runs(u: &Verb, y: &Array, back: bool, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
9645    let promoted = as_items(y);
9646    let base = promoted.as_ref().unwrap_or(y);
9647    let n = base.items();
9648    let m = base.item_size();
9649    // No items, so no runs: the answer's shape cannot come from the cells.
9650    // APL's scan keeps the shape it was given, whatever the function is.
9651    // J's takes the shape of the verb applied to the one run an empty
9652    // argument has, which is the argument itself: `,/\ i.0 3` is a 0 by 0
9653    // table where `+/\ i.0 3` is 0 by 3.
9654    if n == 0 {
9655        if ctx.cfg.rules.lang == crate::Lang::Apl {
9656            return Ok(Array::new(base.shape.clone(), Data::empty(base.dtype())));
9657        }
9658        let cell = u.is_pure().then(|| base.clone());
9659        return Ok(empty_frame(&[0], base.dtype(), cell, ctx, |cell, c| u.monad(cell, c, span)));
9660    }
9661    if n > 0 && base.dtype().is_numeric() && let Some(op) = folded_op(u) {
9662        // Folding from the right is the insert's own order, so it holds
9663        // for any step; folding from the left needs associativity.
9664        if (back || is_associative(op))
9665            && let Some(d) = scan_typed(op, base.row_major_data(), n, m, back)
9666        {
9667            return Ok(Array::new(base.shape.clone(), d));
9668        }
9669    }
9670    if n > 0 && let Verb::Reduce(inner) = u {
9671        if base.dtype().is_numeric()
9672            && let Some(c) = affine_step(inner)
9673            && let Some(d) = affine_scan(c, base, back)
9674        {
9675            return Ok(Array::new(base.shape.clone(), d));
9676        }
9677        // Suffix k is item k folded with suffix k+1, because right to left
9678        // is the insert's own order: one step per item, whatever the verb.
9679        // Prefixes have no such relation — prefix k and prefix k+1 share
9680        // their tail, not their head — so only this direction is a running
9681        // fold in general, and it is the direction `|. u/\. |. y` reverses
9682        // twice to reach.
9683        if back && u.is_pure() {
9684            let mut acc = base.item(n - 1);
9685            let mut cells = Vec::with_capacity(n);
9686            cells.push(acc.clone());
9687            for i in (0..n - 1).rev() {
9688                acc = inner.dyad(&base.item(i), &acc, ctx, span)?;
9689                cells.push(acc.clone());
9690            }
9691            cells.reverse();
9692            return assemble(&[n], cells, span);
9693        }
9694    }
9695    let apl = ctx.cfg.rules.lang == crate::Lang::Apl;
9696    let cells = each_cell(n, n * m, u.is_pure(), ctx, |i, c| {
9697        let part = if back { section(base, i, n) } else { section(base, 0, i + 1) };
9698        u.monad(&part, c, span)
9699    })?;
9700    if apl { assemble_items(&[n], cells, span) } else { assemble(&[n], cells, span) }
9701}
9702
9703/// The result of a window longer than the argument holds no items, but it
9704/// still has the shape of one: J learns that shape by running the verb on a
9705/// window of fills, and so does this. A verb that fails on fills, or a
9706/// window too large to build, leaves the result a plain empty vector.
9707fn empty_windows(u: &Verb, y: &Array, w: usize, ctx: &mut Ctx<'_>, span: Span) -> Array {
9708    let m = y.item_size();
9709    if u.is_pure() && let Some(cells) = w.checked_mul(m).filter(|&s| s <= 1 << 20) {
9710        let mut shape = y.shape.clone();
9711        shape[0] = w;
9712        let probe = Array::new(shape, fill_data(y.dtype(), cells));
9713        if let Ok(cell) = u.monad(&probe, ctx, span) {
9714            let mut shape = vec![0usize];
9715            shape.extend_from_slice(&cell.shape);
9716            return Array::new(shape, Data::empty(cell.dtype()));
9717        }
9718    }
9719    Array::new(vec![0], Data::empty(DType::I64))
9720}
9721
9722/// The window size: one integer atom.
9723fn window_size(x: &Array, near: NearInt, span: Span) -> Result<i64> {
9724    let v = x
9725        .to_i64_vec_near(near)
9726        .ok_or_else(|| Error::domain("the window size must be an integer", span))?;
9727    match v.as_slice() {
9728        [k] => Ok(*k),
9729        _ => Err(Error::new(
9730            ErrorKind::Length,
9731            "the window size must be a single number",
9732            Some(span),
9733        )),
9734    }
9735}
9736
9737/// `x u\ y`: the verb applied to runs of x items.
9738///
9739/// A positive x takes the overlapping windows of that length, of which there
9740/// are none when the argument is shorter; a negative one takes the
9741/// non-overlapping chunks of |x| items, the last of them short; and zero
9742/// takes the n+1 empty runs between and around the items, which is what J
9743/// does with it.
9744fn infix(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
9745    let k = window_size(x, ctx.cfg.near(), span)?;
9746    let promoted = as_items(y);
9747    let base = promoted.as_ref().unwrap_or(y);
9748    let n = base.items();
9749    let m = base.item_size();
9750    if k < 0 {
9751        let w = k.unsigned_abs() as usize;
9752        let count = n.div_ceil(w);
9753        let cells = each_cell(count, n * m, u.is_pure(), ctx, |i, c| {
9754            u.monad(&section(base, i * w, ((i + 1) * w).min(n)), c, span)
9755        })?;
9756        return assemble(&[count], cells, span);
9757    }
9758    let w = k as usize;
9759    if n < w {
9760        return Ok(empty_windows(u, base, w, ctx, span));
9761    }
9762    let count = n - w + 1;
9763    if w > 0 && base.dtype().is_numeric()
9764        && let Some(op) = folded_op(u) && let Some(d) = window_typed(op, &base.data, n, m, w)
9765    {
9766        let mut shape = base.shape.clone();
9767        shape[0] = count;
9768        return Ok(Array::new(shape, d));
9769    }
9770    let work = count.saturating_mul(w).saturating_mul(m);
9771    let cells = each_cell(count, work, u.is_pure(), ctx, |i, c| {
9772        u.monad(&section(base, i, i + w), c, span)
9773    })?;
9774    assemble(&[count], cells, span)
9775}
9776
9777/// `n f/ y` (APL): the reduce of every window of n items along the leading
9778/// axis. `f/` itself decides what folding a window means, so the operand's
9779/// own rules — the identity of an empty fold, the enclosure APL's insert
9780/// puts round a non-scalar value — carry over unchanged.
9781///
9782/// n is one integer. A positive one takes the overlapping windows in order;
9783/// a negative one takes the same windows with their items REVERSED, which
9784/// only shows on a fold that is not commutative (`¯2-/1 2 3` is `1 1` where
9785/// `2-/1 2 3` is `¯1 ¯1`); zero takes the `1+≢y` empty windows, so the
9786/// answer is that many copies of the operand's identity. The axis loses
9787/// `|n|-1` items, so `|n|` may reach `1+≢y` — one item further and there is
9788/// no such window, which is an error rather than a shorter answer.
9789fn nwise(f: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
9790    // How many numbers there are is settled before what they are: a left
9791    // argument of two is a length error whatever it holds, which is what
9792    // keeps `1 1+/2 3` from reading as a compress.
9793    if x.count() != 1 {
9794        return Err(Error::new(
9795            ErrorKind::Length,
9796            "the window size must be a single number",
9797            Some(span),
9798        ));
9799    }
9800    let k = window_size(x, ctx.cfg.near(), span)?;
9801    let promoted = as_items(y);
9802    // A rank-0 argument has no axis to window. One item is what `≢` counts
9803    // it as, and a window of one leaves it exactly as it was, rank included;
9804    // any other window has to make the axis the argument never had.
9805    if promoted.is_some() && k.unsigned_abs() == 1 {
9806        return Ok(y.clone());
9807    }
9808    let base = promoted.as_ref().unwrap_or(y).to_row_major();
9809    let base = &base;
9810    let n = base.items();
9811    let m = base.item_size();
9812    let w = k.unsigned_abs() as usize;
9813    if w > n + 1 {
9814        return Err(Error::domain(
9815            format!("a window of {w} does not fit an axis of {n}"),
9816            span,
9817        ));
9818    }
9819    let count = n + 1 - w;
9820    let fold = Verb::Reduce(Box::new(f.clone()));
9821    if count == 0 {
9822        return Ok(empty_windows(&fold, base, w, ctx, span));
9823    }
9824    // The blockwise fold the infix already has. It runs over whole items at
9825    // full rank, which is what folding the elements along the axis comes to
9826    // for the arithmetic operands it covers, and those are all commutative,
9827    // so a reversed window folds to the same value.
9828    if w > 0
9829        && base.dtype().is_numeric()
9830        && let Some(op) = scalar_dyad_of(f)
9831        && let Some(d) = window_typed(op, base.row_major_data(), n, m, w)
9832    {
9833        let mut shape = base.shape.clone();
9834        shape[0] = count;
9835        return Ok(Array::new(shape, d));
9836    }
9837    let work = count.saturating_mul(w.max(1)).saturating_mul(m);
9838    let cells = each_cell(count, work, f.is_pure(), ctx, |i, c| {
9839        let win = section(base, i, i + w);
9840        let win = if k < 0 { reverse(&win) } else { win };
9841        fold.monad(&win, c, span)
9842    })?;
9843    assemble(&[count], cells, span)
9844}
9845
9846/// `u^:n y` and `x u^:n y`: n applications of the verb, or iteration until
9847/// the result stops changing.
9848fn power(
9849    u: &Verb,
9850    p: Power,
9851    x: Option<&Array>,
9852    y: &Array,
9853    ctx: &mut Ctx<'_>,
9854    span: Span,
9855) -> Result<Array> {
9856    let step = |acc: &Array, c: &mut Ctx<'_>| match x {
9857        Some(x) => u.dyad(x, acc, c, span),
9858        None => u.monad(acc, c, span),
9859    };
9860    match p {
9861        Power::Times(n) => {
9862            let mut acc = y.clone();
9863            for _ in 0..n {
9864                acc = step(&acc, ctx)?;
9865            }
9866            Ok(acc)
9867        }
9868        Power::Converge => {
9869            let mut acc = y.clone();
9870            for _ in 0..CONVERGE_LIMIT {
9871                let next = step(&acc, ctx)?;
9872                if arrays_match(&next, &acc, ctx.cfg.tol) {
9873                    return Ok(next);
9874                }
9875                acc = next;
9876            }
9877            Err(Error::domain("the iteration did not converge", span))
9878        }
9879        // One answer per count. The counts are taken in the order given and
9880        // the walk is shared: the applications are counted from 0 upwards
9881        // and an answer is kept wherever a count asks for it.
9882        Power::Each(ref counts) => {
9883            let mut acc = y.clone();
9884            let mut done = 0u64;
9885            let mut order: Vec<usize> = (0..counts.len()).collect();
9886            order.sort_by_key(|&i| counts[i]);
9887            let mut cells: Vec<Option<Array>> = vec![None; counts.len()];
9888            for i in order {
9889                while done < counts[i] {
9890                    acc = step(&acc, ctx)?;
9891                    done += 1;
9892                }
9893                cells[i] = Some(acc.clone());
9894            }
9895            let cells: Vec<Array> = cells.into_iter().map(|c| c.expect("every count filled")).collect();
9896            assemble(&[cells.len()], cells, span)
9897        }
9898        Power::ConvergeTrace => {
9899            let mut acc = y.clone();
9900            let mut cells = vec![acc.clone()];
9901            for _ in 0..CONVERGE_LIMIT {
9902                let next = step(&acc, ctx)?;
9903                if arrays_match(&next, &acc, ctx.cfg.tol) {
9904                    return assemble(&[cells.len()], cells, span);
9905                }
9906                cells.push(next.clone());
9907                acc = next;
9908            }
9909            Err(Error::domain("the iteration did not converge", span))
9910        }
9911    }
9912}
9913
9914/// `u^:v y` and `x u^:v y` (J): the verb `v` says how many times to apply
9915/// `u`. `(u^:v)^:_` is the while loop the idiom is written with.
9916fn power_v(
9917    u: &Verb,
9918    v: &Verb,
9919    x: Option<&Array>,
9920    y: &Array,
9921    ctx: &mut Ctx<'_>,
9922    span: Span,
9923) -> Result<Array> {
9924    let count = match x {
9925        Some(x) => v.dyad(x, y, ctx, span)?,
9926        None => v.monad(y, ctx, span)?,
9927    };
9928    let n = count
9929        .to_i64_vec_near(ctx.cfg.near())
9930        .ok_or_else(|| Error::domain("the power count must be an integer", span))?;
9931    if n.len() != 1 {
9932        return Err(Error::not_yet("a list of power counts (u^:v with several)", span));
9933    }
9934    let n = n[0];
9935    if n < 0 {
9936        return Err(Error::not_yet("a negative power (the verb's inverse)", span));
9937    }
9938    power(u, Power::Times(n as u64), x, y, ctx, span)
9939}
9940
9941/// `f⍣g y` (APL): apply `f` until `new g old` holds.
9942fn power_until(
9943    u: &Verb,
9944    test: &Verb,
9945    y: &Array,
9946    ctx: &mut Ctx<'_>,
9947    span: Span,
9948) -> Result<Array> {
9949    let mut acc = y.clone();
9950    for _ in 0..CONVERGE_LIMIT {
9951        let next = u.monad(&acc, ctx, span)?;
9952        let done = test.dyad(&next, &acc, ctx, span)?;
9953        let stop = done
9954            .to_f64_vec()
9955            .ok_or_else(|| Error::domain("the ⍣ test must answer with numbers", span))?;
9956        if !stop.is_empty() && stop.iter().all(|&v| v != 0.0) {
9957            return Ok(next);
9958        }
9959        acc = next;
9960    }
9961    Err(Error::domain("the iteration did not converge", span))
9962}
9963
9964/// `f[k]` (APL): `f` applied along axis `k`.
9965///
9966/// The axis is brought to the front, the verb runs on the leading axis, and
9967/// a result that kept the argument's rank has the axis put back — which is
9968/// what separates a reduction (rank drops, axes stay in order) from a scan
9969/// or a reversal (rank kept).
9970fn along_axis(
9971    u: &Verb,
9972    x: Option<&Array>,
9973    y: &Array,
9974    k: usize,
9975    ctx: &mut Ctx<'_>,
9976    span: Span,
9977) -> Result<Array> {
9978    if k >= y.rank().max(1) {
9979        return Err(Error::new(
9980            ErrorKind::Rank,
9981            format!("axis {k} does not exist on an argument of rank {}", y.rank()),
9982            Some(span),
9983        ));
9984    }
9985    let moved = axis_to_front(y, k);
9986    let r = moved.rank();
9987    let out = match x {
9988        Some(x) => u.dyad(x, &moved, ctx, span)?,
9989        None => u.monad(&moved, ctx, span)?,
9990    };
9991    if out.rank() == r {
9992        return Ok(front_to_axis(&out, k));
9993    }
9994    Ok(out)
9995}
9996
9997// ------------------------------------------------- wave 3: search and steps
9998
9999/// `I. y` (J) / `⍸ y` (APL): index `i` repeated `y[i]` times.
10000///
10001/// J applies at rank 1, so a higher-rank argument frames the vector answers;
10002/// APL applies to the whole argument and answers a rank-2-or-higher one with
10003/// one boxed coordinate vector per occurrence.
10004fn where_indices(y: &Array, origin: i64, boxed: bool, near: NearInt, span: Span) -> Result<Array> {
10005    let counts = y
10006        .to_i64_vec_near(near)
10007        .ok_or_else(|| Error::domain("indices needs non-negative integers", span))?;
10008    if counts.iter().any(|&c| c < 0) {
10009        return Err(Error::domain("indices needs non-negative integers", span));
10010    }
10011    if !boxed || y.rank() < 2 {
10012        let mut out = Vec::new();
10013        for (i, &c) in counts.iter().enumerate() {
10014            for _ in 0..c {
10015                out.push(origin + i as i64);
10016            }
10017        }
10018        return Ok(Array::from_i64(out));
10019    }
10020    let r = y.rank();
10021    let mut coord = vec![0usize; r];
10022    let mut out: Vec<Array> = Vec::new();
10023    for &c in &counts {
10024        if c > 0 {
10025            let point =
10026                Array::from_i64(coord.iter().map(|&k| origin + k as i64).collect::<Vec<_>>());
10027            for _ in 0..c {
10028                out.push(point.clone());
10029            }
10030        }
10031        odometer(&mut coord, &y.shape);
10032    }
10033    Ok(Array::new(vec![out.len()], Data::Box(out.into())))
10034}
10035
10036/// `I.^:_1 y`: how many times each index from zero to the largest occurs in
10037/// y, which is the counting vector `I.` was given. An empty argument counts
10038/// nothing.
10039fn indices_inverse(y: &Array, near: NearInt, span: Span) -> Result<Array> {
10040    if y.count() == 0 {
10041        return Ok(Array::empty(DType::I64));
10042    }
10043    let at = y
10044        .to_i64_vec_near(near)
10045        .ok_or_else(|| Error::domain("the obverse of indices needs integers", span))?;
10046    if at.iter().any(|&i| i < 0) {
10047        return Err(Error::domain("the obverse of indices needs non-negative integers", span));
10048    }
10049    let Some(&top) = at.iter().max() else {
10050        return Ok(Array::empty(DType::I64));
10051    };
10052    let mut counts = vec![0i64; top as usize + 1];
10053    for &i in &at {
10054        counts[i as usize] += 1;
10055    }
10056    Ok(Array::from_i64(counts))
10057}
10058
10059/// `x I. y` / `x ⍸ y`: which interval of the ascending `x` each cell of `y`
10060/// falls in — the number of items of `x` strictly below it.
10061///
10062/// `offset` is what the language adds to that count: nothing in J, and
10063/// `⎕IO - 1` in APL, which is what both references answer.
10064fn interval_index(
10065    x: &Array,
10066    y: &Array,
10067    offset: i64,
10068    closed: bool,
10069    tol: Tol,
10070    ord: Grading,
10071    span: Span,
10072) -> Result<Array> {
10073    // Characters, symbols and boxes have an order of their own, and no
10074    // tolerance: the bounds are searched by that order instead of by value.
10075    if !x.dtype().is_numeric() || !y.dtype().is_numeric() {
10076        return ordered_interval_index(x, y, offset, closed, ord, span);
10077    }
10078    let bounds = x
10079        .to_f64_vec()
10080        .ok_or_else(|| Error::domain("interval index needs numeric bounds", span))?;
10081    let vals = y
10082        .to_f64_vec()
10083        .ok_or_else(|| Error::domain("interval index needs numeric values", span))?;
10084    let out: Vec<i64> = vals
10085        .iter()
10086        .map(|&v| {
10087            // APL counts a bound EQUAL to the value, J does not: `1 3 5⍸3`
10088            // is 2 where `1 3 5 I. 3` is 1.
10089            let count =
10090                bounds.iter().filter(|&&b| if closed { !tol.lt(v, b) } else { tol.lt(b, v) });
10091            offset + count.count() as i64
10092        })
10093        .collect();
10094    Ok(Array::new(y.shape.clone(), Data::I64(out.into())))
10095}
10096
10097/// [`interval_index`] over the element types that are ordered but not
10098/// numeric. Both sides must be the same type — a character bound has
10099/// nothing to say about where a symbol falls.
10100fn ordered_interval_index(
10101    x: &Array,
10102    y: &Array,
10103    offset: i64,
10104    closed: bool,
10105    ord: Grading,
10106    span: Span,
10107) -> Result<Array> {
10108    let (xr, yr) = (x.to_row_major(), y.to_row_major());
10109    let (bounds, vals) = (&xr.data, &yr.data);
10110    let cmp = |i: usize, j: usize| -> Option<std::cmp::Ordering> {
10111        match (bounds, vals) {
10112            (Data::Char(p), Data::Char(q)) => Some(p[i].cmp(&q[j])),
10113            (Data::Symbol(p), Data::Symbol(q)) => Some(crate::symbol::cmp(p[i], q[j])),
10114            // J orders boxed values against each other by the same total
10115            // order `/:` grades them with, so `I.` can search among them.
10116            // APL2 gives its nested values no such order, and GNU APL's own
10117            // is an extension libjay does not follow: see divergences.txt.
10118            (Data::Box(p), Data::Box(q)) if ord.tao == Tao::J => {
10119                Some(cmp_items_total(&p[i], &q[j], ord))
10120            }
10121            _ => None,
10122        }
10123    };
10124    let mut out = Vec::with_capacity(y.count());
10125    for j in 0..y.count() {
10126        let mut count = 0i64;
10127        for i in 0..x.count() {
10128            let ord = cmp(i, j).ok_or_else(|| {
10129                Error::domain(
10130                    format!(
10131                        "interval index compares {} bounds with {} values",
10132                        x.dtype().name(),
10133                        y.dtype().name()
10134                    ),
10135                    span,
10136                )
10137            })?;
10138            // APL counts a bound EQUAL to the value, J does not.
10139            count += i64::from(if closed { ord.is_le() } else { ord.is_lt() });
10140        }
10141        out.push(offset + count);
10142    }
10143    Ok(Array::new(y.shape.clone(), Data::I64(out.into())))
10144}
10145
10146/// `i: y` (J): the integers from `-y` to `y`, one step apart. The count is
10147/// `1 + <. 2 * | y`, and a negative argument counts down.
10148fn steps(y: &Array, span: Span) -> Result<Array> {
10149    let vals = y.to_f64_vec().ok_or_else(|| Error::domain("steps needs a number", span))?;
10150    let v = match vals.first() {
10151        Some(&v) if v.is_finite() => v,
10152        _ => return Err(Error::domain("steps needs a finite number", span)),
10153    };
10154    let n = (2.0 * v.abs()).floor();
10155    if n > 1e7 {
10156        return Err(Error::domain("steps would produce too many items", span));
10157    }
10158    let n = n as i64 + 1;
10159    let step = if v < 0.0 { -1.0 } else { 1.0 };
10160    let start = -v;
10161    if v.fract() == 0.0 {
10162        let start = start as i64;
10163        let step = step as i64;
10164        return Ok(Array::from_i64((0..n).map(|k| start + k * step).collect()));
10165    }
10166    Ok(Array::from_f64((0..n).map(|k| start + k as f64 * step).collect()))
10167}
10168
10169/// `x i: y`: where each cell of `y` LAST sits among the items of `x`.
10170fn index_of_last(x: &Array, y: &Array, origin: i64, tol: Tol) -> Array {
10171    let cell_rank = x.rank().saturating_sub(1).min(y.rank());
10172    let frame_rank = y.rank() - cell_rank;
10173    let frame: Vec<usize> = y.shape[..frame_rank].to_vec();
10174    let nf: usize = frame.iter().product();
10175    let items = x.items();
10176    let mut out = Vec::with_capacity(nf);
10177    for i in 0..nf {
10178        let cell = y.cell_at(frame_rank, i);
10179        let at = (0..items)
10180            .rev()
10181            .find(|&j| arrays_match(&cell, &item_or_self(x, j), tol))
10182            .unwrap_or(items);
10183        out.push(origin + at as i64);
10184    }
10185    Array::new(frame, Data::I64(out.into()))
10186}
10187
10188// ----------------------------------------------------------- roll and deal
10189
10190/// `? y` / `?. y`: every element of y replaced by a random value below it.
10191///
10192/// The whole argument is one draw, taken in ravel order, which is what
10193/// makes `?. 5 # 100` five different numbers rather than one repeated.
10194fn roll(
10195    y: &Array,
10196    origin: i64,
10197    fixed: bool,
10198    float_at_zero: bool,
10199    near: NearInt,
10200    span: Span,
10201) -> Result<Array> {
10202    let bounds = y
10203        .to_i64_vec_near(near)
10204        .ok_or_else(|| Error::domain("roll needs whole numbers", span))?;
10205    if bounds.iter().any(|&b| b < 0) {
10206        return Err(Error::domain("roll needs non-negative numbers", span));
10207    }
10208    if !float_at_zero && bounds.contains(&0) {
10209        return Err(Error::domain("? 0 has no value: the range is empty", span));
10210    }
10211    // A zero anywhere makes the whole answer float, as J's does.
10212    let any_zero = bounds.contains(&0);
10213    crate::rng::with(fixed, |g| {
10214        if any_zero {
10215            let out: Vec<f64> = bounds
10216                .iter()
10217                .map(|&b| {
10218                    if b == 0 {
10219                        g.unit()
10220                    } else {
10221                        (origin + g.below(b as u64) as i64) as f64
10222                    }
10223                })
10224                .collect();
10225            return Ok(Array::new(y.shape.clone(), Data::F64(out.into())));
10226        }
10227        let out: Vec<i64> =
10228            bounds.iter().map(|&b| origin + g.below(b as u64) as i64).collect();
10229        Ok(Array::new(y.shape.clone(), Data::I64(out.into())))
10230    })
10231}
10232
10233/// `x ? y` / `x ?. y`: x distinct values drawn from the y below `origin+y`.
10234fn deal(
10235    x: &Array,
10236    y: &Array,
10237    origin: i64,
10238    fixed: bool,
10239    near: NearInt,
10240    span: Span,
10241) -> Result<Array> {
10242    let want = one_whole(x, "the count dealt", near, span)?;
10243    let from = one_whole(y, "the range dealt from", near, span)?;
10244    if want < 0 || from < 0 {
10245        return Err(Error::domain("deal needs non-negative numbers", span));
10246    }
10247    if want > from {
10248        return Err(Error::domain(
10249            format!("cannot deal {want} distinct value(s) from {from}"),
10250            span,
10251        ));
10252    }
10253    if want == 0 {
10254        return Ok(Array::from_i64(Vec::new()));
10255    }
10256    let drawn = crate::rng::with(fixed, |g| g.deal(want as usize, from as u64));
10257    Ok(Array::from_i64(drawn.into_iter().map(|v| v + origin).collect()))
10258}
10259
10260/// One whole number from a one-element argument.
10261fn one_whole(a: &Array, what: &str, near: NearInt, span: Span) -> Result<i64> {
10262    let v = a
10263        .to_i64_vec_near(near)
10264        .ok_or_else(|| Error::domain(format!("{what} must be a whole number"), span))?;
10265    match v[..] {
10266        [n] => Ok(n),
10267        _ => Err(Error::new(
10268            ErrorKind::Rank,
10269            format!("{what} must be one number"),
10270            Some(span),
10271        )),
10272    }
10273}
10274
10275// ------------------------------------------------------------------ primes
10276
10277/// The `n`-th prime, counting from zero (`p: n`).
10278fn nth_prime(n: i64, span: Span) -> Result<i64> {
10279    if n < 0 {
10280        return Err(Error::domain("the prime index must not be negative", span));
10281    }
10282    const LIMIT: i64 = 5_000_000;
10283    if n >= LIMIT {
10284        return Err(Error::domain(
10285            format!("prime index {n} is beyond the {LIMIT}th prime"),
10286            span,
10287        ));
10288    }
10289    // An upper bound for p_n (n counted from zero): n < 6 is tabulated,
10290    // above that Rosser's bound n(ln n + ln ln n) holds.
10291    let k = (n + 1) as f64;
10292    let bound = if n < 6 { 15.0 } else { k * (k.ln() + k.ln().ln()) };
10293    let bound = bound.ceil() as usize + 1;
10294    let mut sieve = vec![true; bound + 1];
10295    sieve[0] = false;
10296    if bound >= 1 {
10297        sieve[1] = false;
10298    }
10299    let mut p = 2usize;
10300    while p * p <= bound {
10301        if sieve[p] {
10302            let mut q = p * p;
10303            while q <= bound {
10304                sieve[q] = false;
10305                q += p;
10306            }
10307        }
10308        p += 1;
10309    }
10310    let mut seen = 0i64;
10311    for (v, &is_p) in sieve.iter().enumerate() {
10312        if is_p {
10313            if seen == n {
10314                return Ok(v as i64);
10315            }
10316            seen += 1;
10317        }
10318    }
10319    Err(Error::internal("the prime sieve was too small"))
10320}
10321
10322/// `q: n`: the prime factors of n, ascending, with multiplicity.
10323fn prime_factors(n: i64, span: Span) -> Result<Vec<i64>> {
10324    if n < 1 {
10325        return Err(Error::domain("prime factors need a positive integer", span));
10326    }
10327    let mut out = Vec::new();
10328    let mut m = n;
10329    let mut d = 2i64;
10330    while d.saturating_mul(d) <= m {
10331        while m % d == 0 {
10332            out.push(d);
10333            m /= d;
10334        }
10335        d += if d == 2 { 1 } else { 2 };
10336    }
10337    if m > 1 {
10338        out.push(m);
10339    }
10340    Ok(out)
10341}
10342
10343// --------------------------------------------------------- matrix division
10344
10345/// Least-squares solution of `a x = b` by Householder QR.
10346///
10347/// `a` is `m` by `n` in row-major order with `m >= n`, `b` is `m` by `k`.
10348/// The answer is `n` by `k`. None when `a` has not got full column rank,
10349/// which both references refuse.
10350fn lstsq(a: &[f64], m: usize, n: usize, b: &[f64], k: usize) -> Option<Vec<f64>> {
10351    // Work on copies: the factorisation overwrites both.
10352    let mut r = a.to_vec();
10353    let mut c = b.to_vec();
10354    let at = |i: usize, j: usize, w: usize| i * w + j;
10355    let scale = a.iter().fold(0.0f64, |acc, v| acc.max(v.abs()));
10356    if scale == 0.0 {
10357        return None;
10358    }
10359    for j in 0..n {
10360        // The Householder vector for column j below the diagonal.
10361        let norm = (j..m).map(|i| r[at(i, j, n)] * r[at(i, j, n)]).sum::<f64>().sqrt();
10362        if norm <= 1e-13 * scale {
10363            return None;
10364        }
10365        let alpha = if r[at(j, j, n)] > 0.0 { -norm } else { norm };
10366        let mut v = vec![0.0f64; m];
10367        for i in j..m {
10368            v[i] = r[at(i, j, n)];
10369        }
10370        v[j] -= alpha;
10371        let vnorm2: f64 = (j..m).map(|i| v[i] * v[i]).sum();
10372        if vnorm2 > 0.0 {
10373            for col in j..n {
10374                let dot: f64 = (j..m).map(|i| v[i] * r[at(i, col, n)]).sum();
10375                let f = 2.0 * dot / vnorm2;
10376                for i in j..m {
10377                    r[at(i, col, n)] -= f * v[i];
10378                }
10379            }
10380            for col in 0..k {
10381                let dot: f64 = (j..m).map(|i| v[i] * c[at(i, col, k)]).sum();
10382                let f = 2.0 * dot / vnorm2;
10383                for i in j..m {
10384                    c[at(i, col, k)] -= f * v[i];
10385                }
10386            }
10387        }
10388    }
10389    // Back-substitute the upper triangle.
10390    let mut x = vec![0.0f64; n * k];
10391    for col in 0..k {
10392        for i in (0..n).rev() {
10393            let mut acc = c[at(i, col, k)];
10394            for j in i + 1..n {
10395                acc -= r[at(i, j, n)] * x[at(j, col, k)];
10396            }
10397            let d = r[at(i, i, n)];
10398            if d.abs() <= 1e-13 * scale {
10399                return None;
10400            }
10401            x[at(i, col, k)] = acc / d;
10402        }
10403    }
10404    Some(x)
10405}
10406
10407/// A numeric argument as an `m` by `n` row-major buffer. Rank 0 is 1 by 1
10408/// and rank 1 is `m` by 1, which is how both references read them.
10409fn as_matrix(a: &Array, span: Span) -> Result<(Vec<f64>, usize, usize)> {
10410    let v = a
10411        .to_f64_vec()
10412        .ok_or_else(|| Error::domain("matrix division needs numeric data", span))?;
10413    match a.rank() {
10414        0 => Ok((v, 1, 1)),
10415        1 => {
10416            let m = a.shape[0];
10417            Ok((v, m, 1))
10418        }
10419        2 => Ok((v, a.shape[0], a.shape[1])),
10420        _ => Err(Error::new(
10421            ErrorKind::Rank,
10422            "matrix division needs an argument of rank 2 or less",
10423            Some(span),
10424        )),
10425    }
10426}
10427
10428/// `%. y` / `⌹ y`: the inverse of a square matrix, or the least-squares
10429/// pseudo-inverse of a taller one. A wider one is refused, as both
10430/// references refuse it.
10431fn matrix_inverse(y: &Array, span: Span) -> Result<Array> {
10432    let (a, m, n) = as_matrix(y, span)?;
10433    if m < n {
10434        return Err(Error::new(
10435            ErrorKind::Length,
10436            format!("cannot invert a {m} by {n} matrix: it has more columns than rows"),
10437            Some(span),
10438        ));
10439    }
10440    let mut eye = vec![0.0f64; m * m];
10441    for i in 0..m {
10442        eye[i * m + i] = 1.0;
10443    }
10444    let x = lstsq(&a, m, n, &eye, m)
10445        .ok_or_else(|| Error::domain("the matrix is singular", span))?;
10446    // A rank-2 argument gives the n by m pseudo-inverse; a vector or scalar
10447    // keeps its own shape, which is what J prints for them.
10448    let shape = if y.rank() == 2 { vec![n, m] } else { y.shape.clone() };
10449    Ok(Array::new(shape, Data::F64(x.into())))
10450}
10451
10452/// `x %. y` / `x ⌹ y`: the least-squares solution of `y a = x`.
10453fn matrix_divide(x: &Array, y: &Array, span: Span) -> Result<Array> {
10454    let (a, m, n) = as_matrix(y, span)?;
10455    let (b, bm, k) = as_matrix(x, span)?;
10456    if bm != m {
10457        return Err(Error::new(
10458            ErrorKind::Length,
10459            format!("the system has {m} rows but the right-hand side has {bm}"),
10460            Some(span),
10461        ));
10462    }
10463    if m < n {
10464        return Err(Error::new(
10465            ErrorKind::Length,
10466            format!("the {m} by {n} system is underdetermined"),
10467            Some(span),
10468        ));
10469    }
10470    let sol = lstsq(&a, m, n, &b, k)
10471        .ok_or_else(|| Error::domain("the system is singular", span))?;
10472    // The right-hand side's own rank decides the answer's: a vector in gives
10473    // one solution vector, a matrix in gives one column per column.
10474    let shape = if x.rank() == 2 { vec![n, k] } else { vec![n] };
10475    Ok(Array::new(shape, Data::F64(sol.into())))
10476}
10477
10478// ----------------------------------------------------- indexing and amend
10479
10480/// `x ⌷ y` (APL2): one scalar index per axis of y.
10481fn squad(x: &Array, y: &Array, origin: i64, leading: bool, near: NearInt, span: Span) -> Result<Array> {
10482    if x.rank() > 1 {
10483        return Err(Error::new(
10484            ErrorKind::Rank,
10485            "the index of ⌷ must be a scalar or a vector",
10486            Some(span),
10487        ));
10488    }
10489    // One item of x per axis of y — per LEADING axis where the dialect
10490    // reads it that way, so a shorter index leaves the trailing axes
10491    // whole. An item is a scalar, which drops its axis, or an enclosed
10492    // vector, which keeps it and selects that many.
10493    let items: Vec<Array> = if x.rank() == 0 { vec![x.clone()] } else { x.cells(1) };
10494    let named = items.len();
10495    if named > y.rank() || (!leading && named != y.rank()) {
10496        return Err(Error::new(
10497            ErrorKind::Rank,
10498            format!("{} index(es) for an argument of rank {}", named, y.rank()),
10499            Some(span),
10500        ));
10501    }
10502    let mut specs = Vec::with_capacity(items.len());
10503    let mut shape = Vec::new();
10504    for (k, item) in items.iter().enumerate() {
10505        let spec = match item.as_boxes() {
10506            Some(bs) if item.rank() == 0 => bs[0].clone(),
10507            _ => item.clone(),
10508        };
10509        let idx = spec
10510            .to_i64_vec_near(near)
10511            .ok_or_else(|| Error::domain("index must be an integer", span))?;
10512        for &i in &idx {
10513            let j = i - origin;
10514            if j < 0 || j as usize >= y.shape[k] {
10515                return Err(Error::domain(
10516                    format!("index {i} is out of range on axis {k}"),
10517                    span,
10518                ));
10519            }
10520        }
10521        shape.extend_from_slice(&spec.shape);
10522        specs.push((spec.shape.clone(), idx));
10523    }
10524    // An index shorter than the rank names the leading axes only; every
10525    // trailing axis comes through whole.
10526    for k in named..y.rank() {
10527        let n = y.shape[k];
10528        shape.push(n);
10529        specs.push((vec![n], (0..n as i64).map(|i| i + origin).collect()));
10530    }
10531    let y = y.to_row_major();
10532    let st = strides(&y.shape);
10533    let total: usize = shape.iter().product();
10534    let mut data = Data::empty(y.dtype());
10535    let mut coord = vec![0usize; shape.len()];
10536    for _ in 0..total {
10537        let mut at = 0usize;
10538        let mut used = 0usize;
10539        for (k, (sshape, idx)) in specs.iter().enumerate() {
10540            let sst = strides(sshape);
10541            let pick: usize = (0..sshape.len()).map(|a| coord[used + a] * sst[a]).sum();
10542            used += sshape.len();
10543            at += (idx[pick] - origin) as usize * st[k];
10544        }
10545        push_elem(&mut data, y.row_major_data(), at);
10546        odometer(&mut coord, &shape);
10547    }
10548    Ok(Array::new(shape, data))
10549}
10550
10551/// One bracket slot of APL indexing: axis `axis` of `y` selected by `x`.
10552///
10553/// A scalar index drops the axis, any other shape splices in. `rank`, when
10554/// it is not zero, is the number of slots the brackets held: the slot that
10555/// sees the whole array checks it, and the others have already been applied
10556/// to a smaller one.
10557fn select_axis(
10558    x: &Array,
10559    y: &Array,
10560    axis: usize,
10561    rank: usize,
10562    origin: i64,
10563    near: NearInt,
10564    span: Span,
10565) -> Result<Array> {
10566    if rank != 0 && y.rank() != rank {
10567        return Err(Error::new(
10568            ErrorKind::Rank,
10569            format!("{rank} index slot(s) for an argument of rank {}", y.rank()),
10570            Some(span),
10571        ));
10572    }
10573    if axis >= y.rank() {
10574        return Err(Error::new(
10575            ErrorKind::Rank,
10576            format!("axis {axis} does not exist on an argument of rank {}", y.rank()),
10577            Some(span),
10578        ));
10579    }
10580    let idx = x
10581        .to_i64_vec_near(near)
10582        .ok_or_else(|| Error::domain("index must be an integer", span))?;
10583    let len = y.shape[axis];
10584    let mut picks = Vec::with_capacity(idx.len());
10585    for &i in &idx {
10586        let j = i - origin;
10587        if j < 0 || j as usize >= len {
10588            return Err(Error::domain(
10589                format!("index {i} is out of range: axis {axis} has {len} items"),
10590                span,
10591            ));
10592        }
10593        picks.push(j as usize);
10594    }
10595    let mut shape = Vec::with_capacity(y.rank() + x.rank());
10596    shape.extend_from_slice(&y.shape[..axis]);
10597    shape.extend_from_slice(&x.shape);
10598    shape.extend_from_slice(&y.shape[axis + 1..]);
10599    let outer: usize = y.shape[..axis].iter().product();
10600    let inner: usize = y.shape[axis + 1..].iter().product();
10601    let mut data = Data::empty(y.dtype());
10602    for o in 0..outer {
10603        for &p in &picks {
10604            let base = (o * len + p) * inner;
10605            for e in 0..inner {
10606                push_elem(&mut data, &y.data, base + e);
10607            }
10608        }
10609    }
10610    Ok(Array::new(shape, data))
10611}
10612
10613/// `x m} y` (J): the items of `y` at the indices `m`, replaced by `x`.
10614///
10615/// `x` is either one item, used at every index, or one item per index.
10616fn amend(m: &Array, x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
10617    if y.rank() == 0 {
10618        return Err(Error::new(ErrorKind::Rank, "cannot amend a scalar", Some(span)));
10619    }
10620    // A boxed m is J's index specification, the same one `{` reads.
10621    if let Some(spec) = m.as_boxes().and_then(<[Array]>::first) {
10622        let spec = index_spec(spec, y, near, span)?;
10623        return amend_spec(&spec, x, y, span);
10624    }
10625    let idx = m
10626        .to_i64_vec_near(near)
10627        .ok_or_else(|| Error::domain("amend indices must be integers", span))?;
10628    let items = y.items() as i64;
10629    let mut at = Vec::with_capacity(idx.len());
10630    for &i in &idx {
10631        let k = if i < 0 { i + items } else { i };
10632        if k < 0 || k >= items {
10633            return Err(Error::domain(
10634                format!("index {i} is out of range: the argument has {items} items"),
10635                span,
10636            ));
10637        }
10638        at.push(k as usize);
10639    }
10640    let cell = y.item_size();
10641    let per_index = if x.count() == cell {
10642        false
10643    } else if x.count() == cell * at.len() {
10644        true
10645    } else {
10646        return Err(Error::new(
10647            ErrorKind::Length,
10648            format!(
10649                "cannot amend {} item(s) of {} element(s) each with {} element(s)",
10650                at.len(),
10651                cell,
10652                x.count()
10653            ),
10654            Some(span),
10655        ));
10656    };
10657    // The result holds both kinds of value, so it takes the wider type:
10658    // amending an integer list with 1.5 gives a float list, as J's does.
10659    let Some(t) = DType::promote(x.dtype(), y.dtype()) else {
10660        return Err(Error::new(
10661            ErrorKind::Type,
10662            "the replacement and the argument hold different kinds of value",
10663            Some(span),
10664        ));
10665    };
10666    let (Some(src), Some(base)) = (x.data.cast(t), y.data.cast(t)) else {
10667        return Err(Error::new(
10668            ErrorKind::Type,
10669            "the replacement and the argument hold different kinds of value",
10670            Some(span),
10671        ));
10672    };
10673    // Rebuild rather than mutate: the buffer may be shared, or foreign.
10674    let mut data = Data::empty(t);
10675    let mut plan: Vec<Option<usize>> = vec![None; y.items()];
10676    for (n, &k) in at.iter().enumerate() {
10677        plan[k] = Some(if per_index { n } else { 0 });
10678    }
10679    for (i, slot) in plan.iter().enumerate() {
10680        match slot {
10681            Some(n) => {
10682                for e in 0..cell {
10683                    push_elem(&mut data, &src, n * cell + e);
10684                }
10685            }
10686            None => {
10687                for e in 0..cell {
10688                    push_elem(&mut data, &base, i * cell + e);
10689                }
10690            }
10691        }
10692    }
10693    Ok(Array::new(y.shape.clone(), data))
10694}
10695
10696/// `x {:: y` (J): follow the path `x` into `y`, opening one level a step.
10697///
10698/// A boxed `x` is one step per box; a simple `x` is a single step, so
10699/// `1 {:: y` is item 1 of y opened once.
10700fn fetch(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
10701    let steps: Vec<Array> = match x.as_boxes() {
10702        Some(bs) => bs.to_vec(),
10703        None => vec![x.clone()],
10704    };
10705    let mut cur = y.clone();
10706    for step in steps {
10707        // An empty step selects the level whole, which is how a path
10708        // reaches into a boxed scalar; `a:` spells it and holds characters.
10709        let idx = if step.count() == 0 {
10710            Vec::new()
10711        } else {
10712            step.to_i64_vec_near(near)
10713                .ok_or_else(|| Error::domain("a fetch path holds integers", span))?
10714        };
10715        // A scalar has one item, which is how `{` reads one too.
10716        let base =
10717            if cur.rank() == 0 { Array::new(vec![1], cur.data.clone()) } else { cur.clone() };
10718        if idx.len() > base.rank() {
10719            return Err(Error::new(
10720                ErrorKind::Length,
10721                format!(
10722                    "a path step of {} index(es) into a value of rank {}",
10723                    idx.len(),
10724                    cur.rank()
10725                ),
10726                Some(span),
10727            ));
10728        }
10729        let at = cell_index(&base, &idx, span)?;
10730        cur = open_cell(&base.cell_at(idx.len(), at));
10731    }
10732    Ok(cur)
10733}
10734
10735/// The cell number a path step names, in the order `cell_at` counts them.
10736fn cell_index(y: &Array, idx: &[i64], span: Span) -> Result<usize> {
10737    let mut at = 0usize;
10738    for (k, &i) in idx.iter().enumerate() {
10739        let len = y.shape[k] as i64;
10740        let j = if i < 0 { i + len } else { i };
10741        if j < 0 || j >= len {
10742            return Err(Error::domain(
10743                format!("index {i} is out of range: axis {k} has {len} items"),
10744                span,
10745            ));
10746        }
10747        at = at * y.shape[k] + j as usize;
10748    }
10749    Ok(at)
10750}
10751
10752// ------------------------------------------------------ partition, groups
10753
10754/// `x ⊂ y` (APL2): partitioned enclose.
10755///
10756/// A partition opens wherever `x` rises — `x[i] > x[i-1]`, reading `x[-1]`
10757/// as zero — and an item whose flag is zero is dropped rather than joined
10758/// to anything. That is what GNU APL answers, and it is what makes
10759/// `1 1 2 2 ⊂ 'abcd'` two pairs rather than one run.
10760/// `x⊂y` in the Dyalog line: a partitioned enclose.
10761///
10762/// Each item of x says how many partitions to open before the item of y
10763/// beside it, so a count above one leaves an empty partition behind and a
10764/// leading zero drops the items ahead of the first partition. The answer
10765/// is a VECTOR of partitions however deep y is: rank 2 and above splits
10766/// the last axis and every partition keeps the axes ahead of it.
10767fn partition_counts(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
10768    if y.rank() == 0 {
10769        return Err(Error::new(
10770            ErrorKind::Rank,
10771            "partitioned enclose needs an array to partition",
10772            Some(span),
10773        ));
10774    }
10775    let counts = x
10776        .to_i64_vec_near(near)
10777        .ok_or_else(|| Error::domain("partition counts must be integers", span))?;
10778    if counts.iter().any(|&c| c < 0) {
10779        return Err(Error::domain("partition counts must not be negative", span));
10780    }
10781    let last = y.shape[y.rank() - 1];
10782    // A scalar count applies to every item; a vector shorter than the
10783    // axis is padded with zeros, so its items stay in the partition
10784    // already open. More counts than items is a length error.
10785    if counts.len() > last {
10786        return Err(Error::new(
10787            ErrorKind::Length,
10788            format!("{} count(s) for {} item(s)", counts.len(), last),
10789            Some(span),
10790        ));
10791    }
10792    let at = |i: usize| -> i64 {
10793        if x.rank() == 0 {
10794            counts.first().copied().unwrap_or(0)
10795        } else {
10796            counts.get(i).copied().unwrap_or(0)
10797        }
10798    };
10799    // Each partition is a contiguous run of the last axis: where it
10800    // starts, and how many items it holds.
10801    let mut groups: Vec<(usize, usize)> = Vec::new();
10802    for i in 0..last {
10803        for _ in 0..at(i) {
10804            groups.push((i, 0));
10805        }
10806        if let Some(g) = groups.last_mut() {
10807            g.1 += 1;
10808        }
10809    }
10810    let y = y.to_row_major();
10811    let rows = if last == 0 { 0 } else { y.count() / last };
10812    let lead = &y.shape[..y.rank() - 1];
10813    let parts: Vec<Array> = groups
10814        .iter()
10815        .map(|&(start, len)| {
10816            let mut d = Data::empty(y.dtype());
10817            for r in 0..rows {
10818                for c in start..start + len {
10819                    push_elem(&mut d, y.row_major_data(), r * last + c);
10820                }
10821            }
10822            let mut shape = lead.to_vec();
10823            shape.push(len);
10824            Array::new(shape, d)
10825        })
10826        .collect();
10827    Ok(Array::new(vec![parts.len()], Data::Box(parts.into())))
10828}
10829
10830fn partition_enclose(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
10831    // Rank 2 and above partitions the LAST axis, once per cross section,
10832    // so the axes ahead of it frame the answer.
10833    if y.rank() > 1 {
10834        let last = y.shape[y.rank() - 1];
10835        let rows = y.count() / last.max(1);
10836        let mut cells: Vec<Array> = Vec::new();
10837        let mut width = None;
10838        for r in 0..rows {
10839            let row = Array::new(vec![last], y.data.slice(r * last, (r + 1) * last));
10840            let parts = partition_enclose(x, &row, near, span)?;
10841            let n = parts.count();
10842            if *width.get_or_insert(n) != n {
10843                return Err(Error::internal("partitions of unequal count"));
10844            }
10845            match parts.data {
10846                Data::Box(v) => cells.extend(v.as_slice().iter().cloned()),
10847                _ => return Err(Error::internal("a partition is boxed")),
10848            }
10849        }
10850        let mut shape = y.shape[..y.rank() - 1].to_vec();
10851        shape.push(width.unwrap_or(0));
10852        return Ok(Array::new(shape, Data::Box(cells.into())));
10853    }
10854    if y.rank() == 0 {
10855        return Err(Error::new(
10856            ErrorKind::Rank,
10857            "partitioned enclose needs an array to partition",
10858            Some(span),
10859        ));
10860    }
10861    // No flag and no item: nothing is ever partitioned, so nothing about
10862    // the flags has to be a flag. `(0⍴⊂⍳3)⊂(0⍴0)` is the empty nested
10863    // vector. Where there ARE items, the flags are read as always — an
10864    // empty flag list against three items stays a length error.
10865    if x.count() == 0 && y.count() == 0 {
10866        return Ok(Array::new(vec![0], Data::Box(Vec::new().into())));
10867    }
10868    let mut flags = x
10869        .to_i64_vec_near(near)
10870        .ok_or_else(|| Error::domain("partition flags must be integers", span))?;
10871    if flags.iter().any(|&f| f < 0) {
10872        return Err(Error::domain("partition flags must not be negative", span));
10873    }
10874    // A SINGLE flag is the flag of every item, so `1⊂1 2 3` opens one
10875    // partition over the whole vector and `0⊂1 2 3` opens none. Only the
10876    // one-flag case extends: two flags for three items stays a length
10877    // error, since there is no reading that makes them fit.
10878    if flags.len() == 1 && y.shape[0] != 1 {
10879        flags = vec![flags[0]; y.shape[0]];
10880    }
10881    if flags.len() != y.shape[0] {
10882        return Err(Error::new(
10883            ErrorKind::Length,
10884            format!("{} flag(s) for {} item(s)", flags.len(), y.shape[0]),
10885            Some(span),
10886        ));
10887    }
10888    let mut parts: Vec<Array> = Vec::new();
10889    let mut cur: Option<Data> = None;
10890    let mut prev = 0i64;
10891    for (i, &f) in flags.iter().enumerate() {
10892        if f > prev {
10893            if let Some(d) = cur.take() {
10894                parts.push(Array::new(vec![d.len()], d));
10895            }
10896            cur = Some(Data::empty(y.dtype()));
10897        }
10898        prev = f;
10899        if f == 0 {
10900            continue;
10901        }
10902        if let Some(d) = cur.as_mut() {
10903            push_elem(d, &y.data, i);
10904        }
10905    }
10906    if let Some(d) = cur.take() {
10907        parts.push(Array::new(vec![d.len()], d));
10908    }
10909    Ok(Array::new(vec![parts.len()], Data::Box(parts.into())))
10910}
10911
10912/// `x u/. y` (J): `u` over each group of items of `y` sharing a key in `x`,
10913/// the groups in the order their keys first appear.
10914fn key(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
10915    let keys = if x.rank() == 0 { Array::new(vec![1], x.data.clone()) } else { x.clone() };
10916    let n = keys.items();
10917    if n != y.items() && !(y.rank() == 0 && n == 1) {
10918        return Err(Error::new(
10919            ErrorKind::Length,
10920            format!("{n} key(s) for {} item(s)", y.items()),
10921            Some(span),
10922        ));
10923    }
10924    let groups = group_positions(&keys, ctx.cfg.tol);
10925    let items = if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() };
10926    let mut cells = Vec::with_capacity(groups.len());
10927    for (_, at) in &groups {
10928        cells.push(u.monad(&select_items(&items, at), ctx, span)?);
10929    }
10930    assemble(&[groups.len()], cells, span)
10931}
10932
10933/// `u/. y` (J): `u` over each anti-diagonal of a table, starting at the
10934/// leading corner.
10935fn oblique(u: &Verb, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
10936    if y.rank() < 2 {
10937        let items = if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() };
10938        let n = items.items();
10939        let mut cells = Vec::with_capacity(n);
10940        for i in 0..n {
10941            cells.push(u.monad(&select_items(&items, &[i]), ctx, span)?);
10942        }
10943        return assemble(&[n], cells, span);
10944    }
10945    if y.rank() > 2 {
10946        return Err(Error::not_yet("oblique (u/.) on a rank-3 or higher argument", span));
10947    }
10948    let (rows, cols) = (y.shape[0], y.shape[1]);
10949    let mut cells = Vec::with_capacity(rows + cols - 1);
10950    for d in 0..rows + cols - 1 {
10951        let mut data = Data::empty(y.dtype());
10952        let mut len = 0usize;
10953        for i in 0..rows {
10954            if d >= i && d - i < cols {
10955                push_elem(&mut data, &y.data, i * cols + (d - i));
10956                len += 1;
10957            }
10958        }
10959        cells.push(u.monad(&Array::new(vec![len], data), ctx, span)?);
10960    }
10961    assemble(&[rows + cols - 1], cells, span)
10962}
10963
10964// ----------------------------------------------------------------- cutting
10965
10966/// Where each interval of a cut begins and ends (both inclusive of the
10967/// start, exclusive of the end).
10968///
10969/// `mode` is J's: 1 and -1 have the fret open an interval, 2 and -2 have it
10970/// close one, and the negative spellings drop the fret itself.
10971fn cut_ranges(frets: &[bool], mode: i64) -> Vec<(usize, usize)> {
10972    let n = frets.len();
10973    let mut out = Vec::new();
10974    if mode.abs() == 1 {
10975        let mut start: Option<usize> = None;
10976        for (i, &fret) in frets.iter().enumerate() {
10977            if fret {
10978                if let Some(s) = start {
10979                    out.push((s, i));
10980                }
10981                start = Some(i);
10982            }
10983        }
10984        if let Some(s) = start {
10985            out.push((s, n));
10986        }
10987        if mode < 0 {
10988            return out.into_iter().map(|(s, e)| (s + 1, e)).collect();
10989        }
10990    } else {
10991        let mut start = 0usize;
10992        for (i, &fret) in frets.iter().enumerate() {
10993            if fret {
10994                out.push((start, i + 1));
10995                start = i + 1;
10996            }
10997        }
10998        if mode < 0 {
10999            return out.into_iter().map(|(s, e)| (s, e - 1)).collect();
11000        }
11001    }
11002    out
11003}
11004
11005/// `x u;.n y` and `u;.n y` (J).
11006fn cut(
11007    u: &Verb,
11008    x: Option<&Array>,
11009    y: &Array,
11010    mode: i64,
11011    ctx: &mut Ctx<'_>,
11012    span: Span,
11013) -> Result<Array> {
11014    if mode == 0 {
11015        let Some(x) = x else {
11016            return u.monad(&reverse_all_axes(y), ctx, span);
11017        };
11018        let (origin, size) = rectangle(x, span)?;
11019        let origin = origin.unwrap_or_else(|| vec![0; size.len()]);
11020        return u.monad(&subarray(y, &origin, &size, span)?, ctx, span);
11021    }
11022    if mode.abs() == 3 {
11023        let Some(x) = x else {
11024            return Err(Error::not_yet("monadic tessellation (u;.3 y)", span));
11025        };
11026        return tessellate(u, x, y, mode < 0, ctx, span);
11027    }
11028    if !matches!(mode, 1 | -1 | 2 | -2) {
11029        return Err(Error::not_yet(format!("cut (u;.{mode})"), span));
11030    }
11031    let items = if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() };
11032    let n = items.items();
11033    let tol = ctx.cfg.tol;
11034    let frets: Vec<bool> = match x {
11035        Some(x) => {
11036            let flags = x.to_i64_vec().ok_or_else(|| {
11037                if x.dtype() == DType::Box {
11038                    // A boxed left argument is J's per-axis form, one box of
11039                    // frets per leading axis. Saying so is the contract:
11040                    // this is a gap, not a domain.
11041                    Error::not_yet("per-axis cut frets (a boxed left argument)", span)
11042                } else {
11043                    Error::domain("cut frets must be integers", span)
11044                }
11045            })?;
11046            // A fret is a flag, and only 0 and 1 are flags: `2 u;.1 y` is
11047            // a domain error, as the reference has it.
11048            if let Some(&bad) = flags.iter().find(|&&f| f != 0 && f != 1) {
11049                return Err(Error::domain(format!("{bad} is not a fret: a fret is 0 or 1"), span));
11050            }
11051            // A scalar fret marks every item, which is the whole of
11052            // `1 u;.2 y`: one interval per item.
11053            if x.rank() == 0 {
11054                vec![flags[0] != 0; n]
11055            } else if flags.is_empty() {
11056                // Marked below: no fret at all is the whole argument.
11057                Vec::new()
11058            } else {
11059                if flags.len() != n {
11060                    return Err(Error::new(
11061                        ErrorKind::Length,
11062                        format!("{} fret(s) for {n} item(s)", flags.len()),
11063                        Some(span),
11064                    ));
11065                }
11066                flags.iter().map(|&f| f != 0).collect()
11067            }
11068        }
11069        None => {
11070            // The fret is the argument's own first or last item.
11071            if n == 0 {
11072                Vec::new()
11073            } else {
11074                let at = if mode.abs() == 1 { 0 } else { n - 1 };
11075                let mark = items.item(at);
11076                (0..n).map(|i| arrays_match(&items.item(i), &mark, tol)).collect()
11077            }
11078        }
11079    };
11080    // A fret list with no frets in it marks nothing, and J reads that as the
11081    // whole argument in ONE piece — `(0$0) <;.1 'abc'` is one box of 'abc',
11082    // and with no fret to drop the negative spellings answer the same piece.
11083    // An argument with no item of its own still has no piece at all.
11084    // A fret list of a higher rank is J's per-axis form, one row of frets
11085    // per leading axis of y, and an empty one names no axis and no piece.
11086    let empty_frets = matches!(x, Some(x) if x.rank() == 1 && x.count() == 0);
11087    let no_axis = matches!(x, Some(x) if x.rank() > 1 && x.count() == 0);
11088    let ranges = if empty_frets && n > 0 {
11089        vec![(0, n)]
11090    } else if empty_frets || no_axis {
11091        Vec::new()
11092    } else {
11093        cut_ranges(&frets, mode)
11094    };
11095    // No frets, so no intervals: the one interval an empty argument offers
11096    // is the empty itself, and the verb applied to it says what shape the
11097    // pieces would have had.
11098    if ranges.is_empty() {
11099        let cell = u.is_pure().then(|| section(&items, 0, 0));
11100        return Ok(empty_frame(&[0], items.dtype(), cell, ctx, |cell, c| u.monad(cell, c, span)));
11101    }
11102    let mut cells = Vec::with_capacity(ranges.len());
11103    for (s, e) in &ranges {
11104        cells.push(u.monad(&section(&items, *s, *e), ctx, span)?);
11105    }
11106    assemble(&[ranges.len()], cells, span)
11107}
11108
11109/// The left argument of `;.0` and `;.3`: one row of origins (or movements)
11110/// and one of sizes. A single vector gives only the sizes.
11111fn rectangle(x: &Array, span: Span) -> Result<(Option<Vec<i64>>, Vec<i64>)> {
11112    let values = x
11113        .to_i64_vec()
11114        .ok_or_else(|| Error::domain("a cut rectangle is whole numbers", span))?;
11115    match x.rank() {
11116        0 | 1 => Ok((None, values)),
11117        2 if x.shape[0] == 2 => {
11118            let n = x.shape[1];
11119            Ok((Some(values[..n].to_vec()), values[n..].to_vec()))
11120        }
11121        _ => Err(Error::new(
11122            ErrorKind::Rank,
11123            "a cut rectangle is a vector of sizes, or two rows of origins and sizes",
11124            Some(span),
11125        )),
11126    }
11127}
11128
11129/// The block of `y` that starts at `origin` and runs `size` along each of
11130/// the leading axes, the rest of them taken whole. A negative size runs the
11131/// same distance and reverses that axis.
11132fn subarray(y: &Array, origin: &[i64], size: &[i64], span: Span) -> Result<Array> {
11133    if origin.len() > y.rank() {
11134        return Err(Error::new(
11135            ErrorKind::Rank,
11136            format!("a cut of {} axis/axes into a rank-{} value", origin.len(), y.rank()),
11137            Some(span),
11138        ));
11139    }
11140    let r = y.rank();
11141    let st = strides(&y.shape);
11142    let mut shape = y.shape.clone();
11143    let mut start = vec![0i64; r];
11144    let mut step = vec![1i64; r];
11145    for k in 0..origin.len() {
11146        // The magnitude is measured in u128 so that a size of i64::MIN — a
11147        // number the program is free to write — is compared rather than
11148        // negated, and the axis check runs before anything is cast down.
11149        let want = u128::from(size[k].unsigned_abs());
11150        let from = if origin[k] < 0 { origin[k] + y.shape[k] as i64 } else { origin[k] };
11151        if from < 0 || u128::from(from.unsigned_abs()) + want > y.shape[k] as u128 {
11152            return Err(Error::domain(
11153                format!("a cut of {want} from {from} leaves axis {k} of {}", y.shape[k]),
11154                span,
11155            ));
11156        }
11157        let len = want as usize;
11158        shape[k] = len;
11159        if size[k] < 0 {
11160            start[k] = from + len as i64 - 1;
11161            step[k] = -1;
11162        } else {
11163            start[k] = from;
11164        }
11165    }
11166    Ok(gather(y, &shape, &start, &step, &st))
11167}
11168
11169/// The elements of `y` at `start + step × coordinate`, shaped `shape`.
11170fn gather(y: &Array, shape: &[usize], start: &[i64], step: &[i64], st: &[usize]) -> Array {
11171    let n: usize = shape.iter().product();
11172    let mut data = Data::empty(y.dtype());
11173    let mut coord = vec![0usize; shape.len()];
11174    for _ in 0..n {
11175        let idx: usize = (0..shape.len())
11176            .map(|k| (start[k] + step[k] * coord[k] as i64) as usize * st[k])
11177            .sum();
11178        push_elem(&mut data, &y.data, idx);
11179        odometer(&mut coord, shape);
11180    }
11181    Array::new(shape.to_vec(), data)
11182}
11183
11184/// `x u;.3 y` and `x u;._3 y`: u over every block of the given size, moved
11185/// by the given step along each axis. `;.3` keeps the short blocks at the
11186/// far edge; `;._3` takes only the complete ones.
11187fn tessellate(
11188    u: &Verb,
11189    x: &Array,
11190    y: &Array,
11191    complete: bool,
11192    ctx: &mut Ctx<'_>,
11193    span: Span,
11194) -> Result<Array> {
11195    // A single vector gives the sizes; the blocks then move one at a time.
11196    let (movement, size) = rectangle(x, span)?;
11197    // A negative size reverses its axis, which is well defined only where
11198    // the movement is written out: given a bare vector of sizes the
11199    // reference answers with something the magnitude plays no part in, and
11200    // libjay will not guess at it.
11201    if size.iter().any(|&s| s < 0) && movement.is_none() {
11202        return Err(Error::not_yet(
11203            "a negative block size without a movement row (x u;.3 y)",
11204            span,
11205        ));
11206    }
11207    let movement = movement.unwrap_or_else(|| vec![1; size.len()]);
11208    if size.len() > y.rank() {
11209        return Err(Error::new(
11210            ErrorKind::Rank,
11211            format!("a tessellation of {} axis/axes into a rank-{} value", size.len(), y.rank()),
11212            Some(span),
11213        ));
11214    }
11215    // The block size and the step are the program's own numbers and may be
11216    // any i64, so how many blocks fit is counted in i128: `size` has no
11217    // negation at i64::MIN and `len + step` overflows for a large step.
11218    let mut frame = Vec::with_capacity(size.len());
11219    for k in 0..size.len() {
11220        let (len, step) = (i128::from(y.shape[k] as i64), i128::from(movement[k]));
11221        let block = i128::from(size[k]).abs();
11222        if step <= 0 {
11223            return Err(Error::domain("a tessellation moves by a positive step", span));
11224        }
11225        let count = if complete {
11226            if len < block { 0 } else { (len - block) / step + 1 }
11227        } else {
11228            (len + step - 1) / step
11229        };
11230        frame.push(count as usize);
11231    }
11232    let total: usize = frame.iter().product();
11233    let mut cells = Vec::with_capacity(total);
11234    let mut coord = vec![0usize; frame.len()];
11235    for _ in 0..total {
11236        let origin: Vec<i64> = (0..frame.len()).map(|k| coord[k] as i64 * movement[k]).collect();
11237        // A block at the far edge is cut short by what is left of the axis;
11238        // a negative size keeps its sign, which reverses that axis.
11239        let block: Vec<i64> = (0..frame.len())
11240            .map(|k| {
11241                let left = i128::from(y.shape[k] as i64 - origin[k]);
11242                let len = i128::from(size[k]).abs().min(left) as i64;
11243                if size[k] < 0 { -len } else { len }
11244            })
11245            .collect();
11246        cells.push(u.monad(&subarray(y, &origin, &block, span)?, ctx, span)?);
11247        odometer(&mut coord, &frame);
11248    }
11249    assemble(&frame, cells, span)
11250}
11251
11252/// Every axis of `y` reversed — what `u;.0 y` applies its verb to.
11253fn reverse_all_axes(y: &Array) -> Array {
11254    if y.rank() == 0 {
11255        return y.clone();
11256    }
11257    let st = strides(&y.shape);
11258    let n = y.count();
11259    let r = y.rank();
11260    let mut data = Data::empty(y.dtype());
11261    let mut coord = vec![0usize; r];
11262    for _ in 0..n {
11263        let idx: usize = (0..r).map(|k| (y.shape[k] - 1 - coord[k]) * st[k]).sum();
11264        push_elem(&mut data, &y.data, idx);
11265        odometer(&mut coord, &y.shape);
11266    }
11267    Array::new(y.shape.clone(), data)
11268}
11269
11270// ------------------------------------------------------------ along an axis
11271
11272/// `y` with axis `k` moved in front of the others, their order kept.
11273fn axis_to_front(y: &Array, k: usize) -> Array {
11274    if k == 0 || y.rank() < 2 {
11275        return y.clone();
11276    }
11277    let r = y.rank();
11278    let src: Vec<usize> = std::iter::once(k).chain((0..r).filter(|&a| a != k)).collect();
11279    permute_axes(y, &src)
11280}
11281
11282/// `y` with its leading axis moved to position `k`.
11283fn front_to_axis(y: &Array, k: usize) -> Array {
11284    if k == 0 || y.rank() < 2 {
11285        return y.clone();
11286    }
11287    let r = y.rank();
11288    // Output axis a reads source axis: the ones before k shift up by one,
11289    // k itself is the source's leading axis, the rest keep their place.
11290    let mut src = Vec::with_capacity(r);
11291    for a in 0..r {
11292        src.push(match a.cmp(&k) {
11293            std::cmp::Ordering::Less => a + 1,
11294            std::cmp::Ordering::Equal => 0,
11295            std::cmp::Ordering::Greater => a,
11296        });
11297    }
11298    permute_axes(y, &src)
11299}
11300
11301/// `x |: y` and `x ⍉ y`: y with each of its axes sent where the left
11302/// argument says. Several axes sharing a destination are run together,
11303/// which is the diagonal, and the result is as long there as the shortest
11304/// of them.
11305fn transpose_to(y: &Array, dest: &[usize], span: Span) -> Result<Array> {
11306    let rank_out = dest.iter().copied().max().map_or(0, |m| m + 1);
11307    let mut out_shape = vec![usize::MAX; rank_out];
11308    for (a, &d) in dest.iter().enumerate() {
11309        out_shape[d] = out_shape[d].min(y.shape[a]);
11310    }
11311    if out_shape.contains(&usize::MAX) {
11312        return Err(Error::new(
11313            ErrorKind::Domain,
11314            "a transpose must name every axis of the result",
11315            Some(span),
11316        ));
11317    }
11318    let y = y.to_row_major();
11319    let st = strides(&y.shape);
11320    let n: usize = out_shape.iter().product();
11321    let mut data = Data::empty(y.dtype());
11322    let mut coord = vec![0usize; rank_out];
11323    for _ in 0..n {
11324        let idx: usize = dest.iter().enumerate().map(|(a, &d)| coord[d] * st[a]).sum();
11325        push_elem(&mut data, &y.data, idx);
11326        odometer(&mut coord, &out_shape);
11327    }
11328    Ok(Array::new(out_shape, data))
11329}
11330
11331/// `x ⍉ y`: x names, for each axis of y in turn, the axis of the result it
11332/// becomes. Two axes given the same destination are run together.
11333fn transpose_apl(x: &Array, y: &Array, io: i64, near: NearInt, span: Span) -> Result<Array> {
11334    let axes = x
11335        .to_i64_vec_near(near)
11336        .ok_or_else(|| Error::domain("a transpose is given whole numbers", span))?;
11337    if axes.len() != y.rank() {
11338        return Err(Error::new(
11339            ErrorKind::Length,
11340            format!("{} axes for a rank-{} value", axes.len(), y.rank()),
11341            Some(span),
11342        ));
11343    }
11344    let mut dest = Vec::with_capacity(axes.len());
11345    for a in axes {
11346        let d = a - io;
11347        if d < 0 || d as usize >= y.rank() {
11348            return Err(Error::new(
11349                ErrorKind::Domain,
11350                format!("axis {a} is outside a rank-{} value", y.rank()),
11351                Some(span),
11352            ));
11353        }
11354        dest.push(d as usize);
11355    }
11356    transpose_to(y, &dest, span)
11357}
11358
11359/// `x |: y`: x names the axes to move to the END, in the order given; the
11360/// rest keep their order in front. A boxed x groups axes, and the axes of
11361/// one group are run together — the diagonal.
11362fn transpose_j(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
11363    let groups: Vec<Vec<i64>> = match x.as_boxes() {
11364        Some(bs) => bs
11365            .iter()
11366            .map(|b| {
11367                b.to_i64_vec_near(near).ok_or_else(|| {
11368                    Error::domain("a transpose is given whole numbers", span)
11369                })
11370            })
11371            .collect::<Result<Vec<_>>>()?,
11372        None => x
11373            .to_i64_vec_near(near)
11374            .ok_or_else(|| Error::domain("a transpose is given whole numbers", span))?
11375            .into_iter()
11376            .map(|a| vec![a])
11377            .collect(),
11378    };
11379    let r = y.rank();
11380    // Which group each axis belongs to; an axis named twice is an error, as
11381    // it is in J.
11382    let mut group_of = vec![None; r];
11383    for (g, axes) in groups.iter().enumerate() {
11384        for &a in axes {
11385            let k = if a < 0 { a + r as i64 } else { a };
11386            if k < 0 || k as usize >= r {
11387                return Err(Error::new(
11388                    ErrorKind::Domain,
11389                    format!("axis {a} is outside a rank-{r} value"),
11390                    Some(span),
11391                ));
11392            }
11393            if group_of[k as usize].is_some() {
11394                return Err(Error::new(
11395                    ErrorKind::Domain,
11396                    format!("axis {a} is named twice in a transpose"),
11397                    Some(span),
11398                ));
11399            }
11400            group_of[k as usize] = Some(g);
11401        }
11402    }
11403    let leading = group_of.iter().filter(|g| g.is_none()).count();
11404    let mut dest = vec![0usize; r];
11405    let mut next = 0;
11406    for a in 0..r {
11407        match group_of[a] {
11408            None => {
11409                dest[a] = next;
11410                next += 1;
11411            }
11412            Some(g) => dest[a] = leading + g,
11413        }
11414    }
11415    transpose_to(y, &dest, span)
11416}
11417
11418/// `y` with output axis `a` reading source axis `src[a]`.
11419fn permute_axes(y: &Array, src: &[usize]) -> Array {
11420    let st = strides(&y.shape);
11421    let out_shape: Vec<usize> = src.iter().map(|&a| y.shape[a]).collect();
11422    let n = y.count();
11423    let mut data = Data::empty(y.dtype());
11424    let mut coord = vec![0usize; src.len()];
11425    for _ in 0..n {
11426        let idx: usize = (0..src.len()).map(|a| coord[a] * st[src[a]]).sum();
11427        push_elem(&mut data, &y.data, idx);
11428        odometer(&mut coord, &out_shape);
11429    }
11430    Array::new(out_shape, data)
11431}
11432
11433// ------------------------------------------------ index specifications
11434
11435/// What a J index specification picks out of an array.
11436struct Spec {
11437    /// How many leading axes of the argument the specification indexes.
11438    width: usize,
11439    /// One coordinate vector per selected cell, in result order.
11440    cells: Vec<Vec<usize>>,
11441    /// The shape the specification contributes; the argument's remaining
11442    /// axes follow it.
11443    shape: Vec<usize>,
11444}
11445
11446/// One index against an axis of `len` elements, counting a negative one
11447/// from the end.
11448fn axis_position(v: i64, len: usize, span: Span) -> Result<usize> {
11449    let p = if v < 0 { v + len as i64 } else { v };
11450    if p < 0 || p >= len as i64 {
11451        return Err(Error::domain(
11452            format!("index {v} is out of range: the axis has {len} element(s)"),
11453            span,
11454        ));
11455    }
11456    Ok(p as usize)
11457}
11458
11459/// J's index specification: what a BOXED left argument of `{` or `m}` says.
11460///
11461/// `<A` with a simple `A` reads A's last axis as one index per leading axis
11462/// of y, the axes ahead of it framing the result — so `(<1 2) { y` is one
11463/// element and `(<2 2$…) { y` is two of them. `<(c0;c1;…)` gives one
11464/// component per leading axis instead: a simple component's atoms are that
11465/// axis's indices, a scalar one dropping the axis from the result, and a
11466/// BOXED component is the complement — every index of the axis except the
11467/// ones it holds, which is what `a:` (the empty box) uses to mean "all".
11468fn index_spec(content: &Array, y: &Array, near: NearInt, span: Span) -> Result<Spec> {
11469    let too_deep = |n: usize| {
11470        Error::new(
11471            ErrorKind::Rank,
11472            format!("an index specification of {n} axis/axes into a rank-{} value", y.rank()),
11473            Some(span),
11474        )
11475    };
11476    if let Some(items) = content.as_boxes() {
11477        if items.len() > y.rank() {
11478            return Err(too_deep(items.len()));
11479        }
11480        let mut per_axis: Vec<Vec<usize>> = Vec::with_capacity(items.len());
11481        let mut shape: Vec<usize> = Vec::new();
11482        for (k, c) in items.iter().enumerate() {
11483            let len = y.shape[k];
11484            if c.as_boxes().is_some() {
11485                let inner = open_cell(c);
11486                let excluded = inner.to_i64_vec_near(near).ok_or_else(|| {
11487                    Error::domain("an index complement holds integers", span)
11488                })?;
11489                let mut dropped = vec![false; len];
11490                for v in excluded {
11491                    dropped[axis_position(v, len, span)?] = true;
11492                }
11493                let kept: Vec<usize> = (0..len).filter(|i| !dropped[*i]).collect();
11494                shape.push(kept.len());
11495                per_axis.push(kept);
11496            } else {
11497                let idx = c
11498                    .to_i64_vec_near(near)
11499                    .ok_or_else(|| Error::domain("an index holds integers", span))?;
11500                let mut positions = Vec::with_capacity(idx.len());
11501                for v in idx {
11502                    positions.push(axis_position(v, len, span)?);
11503                }
11504                shape.extend_from_slice(&c.shape);
11505                per_axis.push(positions);
11506            }
11507        }
11508        // The components run as an odometer, the last one fastest.
11509        let mut cells: Vec<Vec<usize>> = vec![Vec::new()];
11510        for positions in &per_axis {
11511            let mut next = Vec::with_capacity(cells.len() * positions.len());
11512            for prefix in &cells {
11513                for &p in positions {
11514                    let mut cell = prefix.clone();
11515                    cell.push(p);
11516                    next.push(cell);
11517                }
11518            }
11519            cells = next;
11520        }
11521        return Ok(Spec { width: per_axis.len(), cells, shape });
11522    }
11523    let idx = content
11524        .to_i64_vec_near(near)
11525        .ok_or_else(|| Error::domain("an index specification holds integers", span))?;
11526    let rank = content.rank();
11527    let width = if rank == 0 { 1 } else { content.shape[rank - 1] };
11528    if width > y.rank() {
11529        return Err(too_deep(width));
11530    }
11531    let shape: Vec<usize> = if rank == 0 { Vec::new() } else { content.shape[..rank - 1].to_vec() };
11532    let count: usize = shape.iter().product();
11533    let mut cells: Vec<Vec<usize>> = Vec::new();
11534    if width == 0 {
11535        cells.resize(count, Vec::new());
11536    } else {
11537        for chunk in idx.chunks(width) {
11538            let mut cell = Vec::with_capacity(width);
11539            for (k, &v) in chunk.iter().enumerate() {
11540                cell.push(axis_position(v, y.shape[k], span)?);
11541            }
11542            cells.push(cell);
11543        }
11544    }
11545    Ok(Spec { width, cells, shape })
11546}
11547
11548/// The offset of a cell's first element, given the argument's strides.
11549fn spec_offset(st: &[usize], cell: &[usize]) -> usize {
11550    cell.iter().enumerate().map(|(k, &p)| p * st[k]).sum()
11551}
11552
11553/// `(<spec) { y`: the cells the specification names, in its own order.
11554fn select_spec(spec: &Spec, y: &Array) -> Array {
11555    let st = strides(&y.shape);
11556    let size: usize = y.shape[spec.width..].iter().product();
11557    let mut data = Data::empty(y.dtype());
11558    for cell in &spec.cells {
11559        let base = spec_offset(&st, cell);
11560        for e in 0..size {
11561            push_elem(&mut data, &y.data, base + e);
11562        }
11563    }
11564    let mut shape = spec.shape.clone();
11565    shape.extend_from_slice(&y.shape[spec.width..]);
11566    Array::new(shape, data)
11567}
11568
11569/// `x (<spec)} y`: y with the cells the specification names replaced by x,
11570/// which is either one cell spread over all of them or one cell each.
11571fn amend_spec(spec: &Spec, x: &Array, y: &Array, span: Span) -> Result<Array> {
11572    let size: usize = y.shape[spec.width..].iter().product();
11573    let per_cell = if x.count() == size {
11574        false
11575    } else if x.count() == size * spec.cells.len() {
11576        true
11577    } else {
11578        return Err(Error::new(
11579            ErrorKind::Length,
11580            format!(
11581                "cannot amend {} cell(s) of {size} element(s) each with {} element(s)",
11582                spec.cells.len(),
11583                x.count()
11584            ),
11585            Some(span),
11586        ));
11587    };
11588    let mismatch = || {
11589        Error::new(
11590            ErrorKind::Type,
11591            "the replacement and the argument hold different kinds of value",
11592            Some(span),
11593        )
11594    };
11595    let t = DType::promote(x.dtype(), y.dtype()).ok_or_else(mismatch)?;
11596    let (Some(src), Some(base)) = (x.data.cast(t), y.data.cast(t)) else {
11597        return Err(mismatch());
11598    };
11599    let st = strides(&y.shape);
11600    let mut plan: Vec<Option<usize>> = vec![None; y.count()];
11601    for (n, cell) in spec.cells.iter().enumerate() {
11602        let at = spec_offset(&st, cell);
11603        for e in 0..size {
11604            plan[at + e] = Some(if per_cell { n * size + e } else { e });
11605        }
11606    }
11607    let mut data = Data::empty(t);
11608    for (i, slot) in plan.iter().enumerate() {
11609        match slot {
11610            Some(n) => push_elem(&mut data, &src, *n),
11611            None => push_elem(&mut data, &base, i),
11612        }
11613    }
11614    Ok(Array::new(y.shape.clone(), data))
11615}
11616
11617// -------------------------------------------------------------- the map
11618
11619/// J monadic `{::`: y's box structure with every leaf replaced by the path
11620/// that fetches it.
11621///
11622/// A path is a boxed list holding one index per level descended — the
11623/// coordinate vector within that level's array, empty where the level is a
11624/// boxed scalar. An unboxed y is one leaf, itself, and its path is empty.
11625fn map_paths(y: &Array) -> Array {
11626    fn coord_of(shape: &[usize], mut i: usize) -> Array {
11627        let mut out = vec![0i64; shape.len()];
11628        for k in (0..shape.len()).rev() {
11629            out[k] = (i % shape[k]) as i64;
11630            i /= shape[k];
11631        }
11632        Array::from_i64(out)
11633    }
11634    fn go(y: &Array, prefix: &[Array]) -> Array {
11635        let Some(boxes) = y.as_boxes() else {
11636            if prefix.is_empty() {
11637                return Array::new(vec![0], Data::I64(Vec::new().into()));
11638            }
11639            return Array::new(vec![prefix.len()], Data::Box(prefix.to_vec().into()));
11640        };
11641        let cells: Vec<Array> = boxes
11642            .iter()
11643            .enumerate()
11644            .map(|(i, b)| {
11645                let mut path = prefix.to_vec();
11646                path.push(coord_of(&y.shape, i));
11647                go(b, &path)
11648            })
11649            .collect();
11650        Array::new(y.shape.clone(), Data::Box(cells.into()))
11651    }
11652    go(y, &[])
11653}
11654
11655// ------------------------------------------------------- fill and shift
11656
11657/// `x |.!.f y`: shift along each axis instead of rotating, so an item moved
11658/// past an end is dropped and the place it left takes the fill f.
11659fn shift_fill(
11660    x: &Array,
11661    y: &Array,
11662    fill: &Array,
11663    near: NearInt,
11664    span: Span,
11665) -> Result<Array> {
11666    let counts = axis_counts(x, "shift", near, span)?;
11667    if y.rank() == 0 {
11668        return Ok(y.clone());
11669    }
11670    if counts.len() > y.rank() {
11671        return Err(Error::new(
11672            ErrorKind::Length,
11673            format!("shift has {} amounts for an argument of rank {}", counts.len(), y.rank()),
11674            Some(span),
11675        ));
11676    }
11677    if fill.count() != 1 {
11678        return Err(Error::new(ErrorKind::Length, "a fill is one atom", Some(span)));
11679    }
11680    let mismatch = || {
11681        Error::new(ErrorKind::Type, "the fill and the argument differ in kind", Some(span))
11682    };
11683    let t = DType::promote(y.dtype(), fill.dtype()).ok_or_else(mismatch)?;
11684    let (Some(base), Some(f)) = (y.data.cast(t), fill.data.cast(t)) else {
11685        return Err(mismatch());
11686    };
11687    let st = strides(&y.shape);
11688    let r = y.rank();
11689    let mut data = Data::empty(t);
11690    let mut coord = vec![0usize; r];
11691    for _ in 0..y.count() {
11692        let mut idx = 0usize;
11693        let mut vacated = false;
11694        for k in 0..r {
11695            // Saturating: an amount that cannot be added to the coordinate
11696            // has carried the item past the end of the axis by any measure,
11697            // which is what a shift vacates.
11698            let from = (coord[k] as i64).saturating_add(counts.get(k).copied().unwrap_or(0));
11699            if from < 0 || from >= y.shape[k] as i64 {
11700                vacated = true;
11701                break;
11702            }
11703            idx += from as usize * st[k];
11704        }
11705        if vacated {
11706            push_elem(&mut data, &f, 0);
11707        } else {
11708            push_elem(&mut data, &base, idx);
11709        }
11710        odometer(&mut coord, &y.shape);
11711    }
11712    Ok(Array::new(y.shape.clone(), data))
11713}
11714
11715// ---------------------------------------------------------------- memo
11716
11717/// An exact key for one array, appended to `out`. False where the value has
11718/// no cheap key — an exact number — and the memo must simply not cache it.
11719fn memo_key(a: &Array, out: &mut Vec<u64>) -> bool {
11720    out.push(a.rank() as u64);
11721    out.extend(a.shape.iter().map(|&n| n as u64));
11722    out.push(a.dtype() as u64);
11723    match &a.data {
11724        Data::Ext(_) | Data::Rat(_) => false,
11725        Data::Box(items) => items.iter().all(|item| memo_key(item, out)),
11726        d => {
11727            for i in 0..d.len() {
11728                out.push(elem_key(d, i));
11729            }
11730            true
11731        }
11732    }
11733}
11734
11735/// `u M.`: u's answer for these arguments, computed once and kept.
11736fn memoised(
11737    u: &Verb,
11738    cache: &MemoCache,
11739    x: Option<&Array>,
11740    y: &Array,
11741    ctx: &mut Ctx<'_>,
11742    span: Span,
11743) -> Result<Array> {
11744    let apply = |ctx: &mut Ctx<'_>| match x {
11745        Some(x) => u.dyad(x, y, ctx, span),
11746        None => u.monad(y, ctx, span),
11747    };
11748    let mut key = vec![u64::from(x.is_some())];
11749    let keyed = x.is_none_or(|x| memo_key(x, &mut key)) && memo_key(y, &mut key);
11750    if !keyed {
11751        return apply(ctx);
11752    }
11753    if let Ok(map) = cache.lock() && let Some(hit) = map.get(&key) {
11754        return Ok(hit.clone());
11755    }
11756    let out = apply(ctx)?;
11757    if let Ok(mut map) = cache.lock() {
11758        map.insert(key, out.clone());
11759    }
11760    Ok(out)
11761}
11762
11763// ----------------------------------------------------- levels and spread
11764
11765/// `u L: n y` and `u S: n y`: u over every subarray at boxing level n or
11766/// below. `L:` puts each answer back where its operand was; `S:` collects
11767/// them into the items of one array.
11768fn at_level(
11769    u: &Verb,
11770    level: i64,
11771    spread: bool,
11772    y: &Array,
11773    ctx: &mut Ctx<'_>,
11774    span: Span,
11775) -> Result<Array> {
11776    // A negative level counts down from the argument's own top.
11777    let n = if level < 0 { (boxing_level(y) + level).max(0) } else { level };
11778    if !spread {
11779        return map_level(u, n, y, ctx, span);
11780    }
11781    let mut cells = Vec::new();
11782    collect_level(u, n, y, ctx, span, &mut cells)?;
11783    let count = cells.len();
11784    assemble(&[count], cells, span)
11785}
11786
11787fn map_level(u: &Verb, n: i64, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
11788    let Some(boxes) = y.as_boxes().filter(|_| boxing_level(y) > n) else {
11789        return u.monad(y, ctx, span);
11790    };
11791    let boxes = boxes.to_vec();
11792    let mut cells = Vec::with_capacity(boxes.len());
11793    for b in &boxes {
11794        cells.push(map_level(u, n, b, ctx, span)?);
11795    }
11796    Ok(Array::new(y.shape.clone(), Data::Box(cells.into())))
11797}
11798
11799/// `x u L: n y` and `x u S: n y`: both arguments are descended together
11800/// until each has reached level n, and u is applied to the pair. A side
11801/// that has already reached its level is held while the other descends, so
11802/// an unboxed left argument reaches every leaf of the right one.
11803fn at_level_dyad(
11804    u: &Verb,
11805    level: i64,
11806    spread: bool,
11807    x: &Array,
11808    y: &Array,
11809    ctx: &mut Ctx<'_>,
11810    span: Span,
11811) -> Result<Array> {
11812    // A negative level counts down from each argument's own top, so the
11813    // two sides can stop at different depths.
11814    let depth = |a: &Array| if level < 0 { (boxing_level(a) + level).max(0) } else { level };
11815    let (nx, ny) = (depth(x), depth(y));
11816    if !spread {
11817        return map_level_dyad(u, nx, ny, x, y, ctx, span);
11818    }
11819    let mut cells = Vec::new();
11820    collect_level_dyad(u, nx, ny, x, y, ctx, span, &mut cells)?;
11821    let count = cells.len();
11822    assemble(&[count], cells, span)
11823}
11824
11825/// The boxes to descend into on each side, and the shape the answer takes.
11826struct LevelPairs {
11827    left: Vec<Array>,
11828    right: Vec<Array>,
11829    shape: Vec<usize>,
11830}
11831
11832/// One step of the descent. `None` where neither side has any box left,
11833/// which is where u applies.
11834fn level_pairs(
11835    nx: i64,
11836    ny: i64,
11837    x: &Array,
11838    y: &Array,
11839    span: Span,
11840) -> Result<Option<LevelPairs>> {
11841    let bx = x.as_boxes().filter(|_| boxing_level(x) > nx);
11842    let by = y.as_boxes().filter(|_| boxing_level(y) > ny);
11843    Ok(match (bx, by) {
11844        (None, None) => None,
11845        (Some(bx), None) => {
11846            let n = bx.len();
11847            Some(LevelPairs {
11848                left: bx.to_vec(),
11849                right: vec![y.clone(); n],
11850                shape: x.shape.clone(),
11851            })
11852        }
11853        (None, Some(by)) => {
11854            let n = by.len();
11855            Some(LevelPairs {
11856                left: vec![x.clone(); n],
11857                right: by.to_vec(),
11858                shape: y.shape.clone(),
11859            })
11860        }
11861        (Some(bx), Some(by)) => {
11862            if x.shape != y.shape {
11863                return Err(Error::new(
11864                    ErrorKind::Length,
11865                    format!(
11866                        "the levels do not agree: left shape {}, right shape {}",
11867                        show_shape(&x.shape),
11868                        show_shape(&y.shape)
11869                    ),
11870                    Some(span),
11871                ));
11872            }
11873            Some(LevelPairs { left: bx.to_vec(), right: by.to_vec(), shape: x.shape.clone() })
11874        }
11875    })
11876}
11877
11878fn map_level_dyad(
11879    u: &Verb,
11880    nx: i64,
11881    ny: i64,
11882    x: &Array,
11883    y: &Array,
11884    ctx: &mut Ctx<'_>,
11885    span: Span,
11886) -> Result<Array> {
11887    let Some(step) = level_pairs(nx, ny, x, y, span)? else {
11888        return u.dyad(x, y, ctx, span);
11889    };
11890    let mut cells = Vec::with_capacity(step.left.len());
11891    for (a, b) in step.left.iter().zip(step.right.iter()) {
11892        cells.push(map_level_dyad(u, nx, ny, a, b, ctx, span)?);
11893    }
11894    Ok(Array::new(step.shape, Data::Box(cells.into())))
11895}
11896
11897#[allow(clippy::too_many_arguments)]
11898fn collect_level_dyad(
11899    u: &Verb,
11900    nx: i64,
11901    ny: i64,
11902    x: &Array,
11903    y: &Array,
11904    ctx: &mut Ctx<'_>,
11905    span: Span,
11906    out: &mut Vec<Array>,
11907) -> Result<()> {
11908    let Some(step) = level_pairs(nx, ny, x, y, span)? else {
11909        out.push(u.dyad(x, y, ctx, span)?);
11910        return Ok(());
11911    };
11912    for (a, b) in step.left.iter().zip(step.right.iter()) {
11913        collect_level_dyad(u, nx, ny, a, b, ctx, span, out)?;
11914    }
11915    Ok(())
11916}
11917
11918fn collect_level(
11919    u: &Verb,
11920    n: i64,
11921    y: &Array,
11922    ctx: &mut Ctx<'_>,
11923    span: Span,
11924    out: &mut Vec<Array>,
11925) -> Result<()> {
11926    let Some(boxes) = y.as_boxes().filter(|_| boxing_level(y) > n) else {
11927        out.push(u.monad(y, ctx, span)?);
11928        return Ok(());
11929    };
11930    let boxes = boxes.to_vec();
11931    for b in &boxes {
11932        collect_level(u, n, b, ctx, span, out)?;
11933    }
11934    Ok(())
11935}
11936
11937// --------------------------------------------------------- polynomials
11938
11939/// The ascending coefficients of a polynomial argument, as complex values.
11940fn poly_coeffs(y: &Array, span: Span) -> Result<Vec<Cx>> {
11941    let c = y
11942        .data
11943        .cast(DType::Complex)
11944        .ok_or_else(|| Error::domain("a polynomial's coefficients are numbers", span))?;
11945    match c {
11946        Data::Complex(v) => Ok(v.as_slice().to_vec()),
11947        _ => Err(Error::internal("coefficients did not cast to complex")),
11948    }
11949}
11950
11951/// The same, where an argument with no elements is no coefficient at all
11952/// rather than a type to refuse. A polynomial with no coefficients is the
11953/// zero one and a root form with no roots is its multiplier, so `p. (0$'a')`
11954/// and `(1;0$'a') p. 4` both answer. J keeps the strict reading for the
11955/// integral's argument, which is why the two live side by side.
11956fn poly_coeffs_relaxed(y: &Array, span: Span) -> Result<Vec<Cx>> {
11957    if y.count() == 0 {
11958        return Ok(Vec::new());
11959    }
11960    poly_coeffs(y, span)
11961}
11962
11963/// The ascending coefficients a boxed root form stands for: `m × (x-r0) ×
11964/// (x-r1) × …`, multiplied out.
11965fn root_form_coeffs(parts: &[Array], span: Span) -> Result<Vec<Cx>> {
11966    let (multiplier, roots) = root_form(parts, span)?;
11967    let mut coeffs = vec![multiplier];
11968    for r in poly_coeffs_relaxed(roots, span)? {
11969        let mut next = vec![cx::ZERO; coeffs.len() + 1];
11970        for (k, &c) in coeffs.iter().enumerate() {
11971            next[k + 1] = cx::add(next[k + 1], c);
11972            next[k] = cx::sub(next[k], cx::mul(c, r));
11973        }
11974        coeffs = next;
11975    }
11976    Ok(coeffs)
11977}
11978
11979/// The multiplier and the roots a boxed polynomial argument holds. J writes
11980/// the form as `multiplier ; roots` and lets the multiplier go unsaid: one
11981/// box is the roots alone, with a multiplier of 1.
11982fn root_form(parts: &[Array], span: Span) -> Result<(Cx, &Array)> {
11983    match parts {
11984        [roots] => Ok((cx::ONE, roots)),
11985        [multiplier, roots] => Ok((
11986            poly_coeffs_relaxed(multiplier, span)?.first().copied().unwrap_or(cx::ONE),
11987            roots,
11988        )),
11989        _ => Err(Error::domain("the root form of a polynomial is `multiplier ; roots`", span)),
11990    }
11991}
11992
11993// --------------------------------------------------- hypergeometric series
11994
11995/// Terms the series is allowed before it is called divergent.
11996const HYPERGEOMETRIC_TERMS: usize = 1 << 16;
11997
11998/// A parameter list, for a derived verb's name.
11999fn cx_list(v: &[Cx]) -> String {
12000    v.iter()
12001        .map(|z| if z[1] == 0.0 { format!("{}", z[0]) } else { format!("{}j{}", z[0], z[1]) })
12002        .collect::<Vec<_>>()
12003        .join(" ")
12004}
12005
12006/// `(m H. n) y`: the generalised hypergeometric function, summed term by
12007/// term from the ratio between neighbours —
12008/// `t[k+1] = t[k] × (Π(m+k) ÷ Π(n+k)) × y ÷ (k+1)`.
12009///
12010/// A parameter on both sides contributes the same factor to each product,
12011/// so the pairs are cancelled first: that is what makes `0 H. 0` the
12012/// exponential rather than a term of `0÷0`.
12013fn hypergeometric(num: &[Cx], den: &[Cx], y: &Array, span: Span) -> Result<Array> {
12014    let (num, den) = cancel_parameters(num, den);
12015    let at = poly_coeffs(y, span)?;
12016    let mut out = Vec::with_capacity(at.len());
12017    for z in &at {
12018        out.push(hypergeometric_at(&num, &den, *z, span)?);
12019    }
12020    let mut a = complex_or_real(out);
12021    a.shape = y.shape.clone();
12022    Ok(a)
12023}
12024
12025/// The parameters left once every value common to both lists is dropped
12026/// from each, one occurrence at a time.
12027fn cancel_parameters(num: &[Cx], den: &[Cx]) -> (Vec<Cx>, Vec<Cx>) {
12028    let mut left: Vec<Cx> = Vec::with_capacity(num.len());
12029    let mut right: Vec<Cx> = den.to_vec();
12030    for a in num {
12031        match right.iter().position(|b| b == a) {
12032            Some(i) => {
12033                right.remove(i);
12034            }
12035            None => left.push(*a),
12036        }
12037    }
12038    (left, right)
12039}
12040
12041fn hypergeometric_at(num: &[Cx], den: &[Cx], z: Cx, span: Span) -> Result<Cx> {
12042    // Wholly real arguments are summed in real arithmetic, where dividing
12043    // by a zero parameter gives the infinity J answers with; the complex
12044    // quotient would make that same division a NaN in both parts.
12045    let real = |v: &[Cx]| v.iter().all(|c| c[1] == 0.0);
12046    if z[1] == 0.0 && real(num) && real(den) {
12047        let n: Vec<f64> = num.iter().map(|c| c[0]).collect();
12048        let d: Vec<f64> = den.iter().map(|c| c[0]).collect();
12049        return Ok([hypergeometric_real(&n, &d, z[0], span)?, 0.0]);
12050    }
12051    let mut sum = cx::ONE;
12052    let mut term = cx::ONE;
12053    for k in 0..HYPERGEOMETRIC_TERMS {
12054        let kk = [k as f64, 0.0];
12055        let mut ratio = z;
12056        for a in num {
12057            ratio = cx::mul(ratio, cx::add(*a, kk));
12058        }
12059        for b in den {
12060            ratio = cx::div(ratio, cx::add(*b, kk));
12061        }
12062        term = cx::div(cx::mul(term, ratio), [k as f64 + 1.0, 0.0]);
12063        if !term[0].is_finite() || !term[1].is_finite() {
12064            // A zero denominator parameter, or a term past the range of a
12065            // double: the sum is the infinity (or NaN) the term became.
12066            return Ok(term);
12067        }
12068        let before = sum;
12069        sum = cx::add(sum, term);
12070        // The series has converged once a term no longer moves the sum.
12071        if sum == before {
12072            return Ok(sum);
12073        }
12074    }
12075    Err(Error::domain(
12076        format!("the hypergeometric series did not converge within {HYPERGEOMETRIC_TERMS} terms"),
12077        span,
12078    ))
12079}
12080
12081fn hypergeometric_real(num: &[f64], den: &[f64], z: f64, span: Span) -> Result<f64> {
12082    let mut sum = 1.0f64;
12083    let mut term = 1.0f64;
12084    for k in 0..HYPERGEOMETRIC_TERMS {
12085        let kk = k as f64;
12086        let mut ratio = z;
12087        for a in num {
12088            ratio *= a + kk;
12089        }
12090        for b in den {
12091            ratio /= b + kk;
12092        }
12093        term = term * ratio / (kk + 1.0);
12094        if !term.is_finite() {
12095            return Ok(term);
12096        }
12097        let before = sum;
12098        sum += term;
12099        if sum == before {
12100            return Ok(sum);
12101        }
12102    }
12103    Err(Error::domain(
12104        format!("the hypergeometric series did not converge within {HYPERGEOMETRIC_TERMS} terms"),
12105        span,
12106    ))
12107}
12108
12109/// A complex vector as an array, real where every imaginary part is zero.
12110fn complex_or_real(values: Vec<Cx>) -> Array {
12111    if values.iter().all(|z| z[1] == 0.0) {
12112        return Array::from_f64(values.iter().map(|z| z[0]).collect());
12113    }
12114    Array::new(vec![values.len()], Data::Complex(values.into()))
12115}
12116
12117/// `x p. y`: the polynomial with ascending coefficients x, at y — Horner's
12118/// rule, or the product over the roots when x is the boxed root form.
12119fn poly_eval(x: &Array, y: &Array, span: Span) -> Result<Array> {
12120    let at = poly_coeffs(y, span)?;
12121    let at = at.first().copied().unwrap_or(cx::ZERO);
12122    let value = match x.as_boxes() {
12123        Some(parts) => {
12124            let (mut v, roots) = root_form(parts, span)?;
12125            for r in poly_coeffs_relaxed(roots, span)? {
12126                v = cx::mul(v, cx::sub(at, r));
12127            }
12128            v
12129        }
12130        None => {
12131            let c = poly_coeffs_relaxed(x, span)?;
12132            let mut v = cx::ZERO;
12133            for &k in c.iter().rev() {
12134                v = cx::add(cx::mul(v, at), k);
12135            }
12136            v
12137        }
12138    };
12139    Ok(scalar_complex_or_real(value))
12140}
12141
12142fn scalar_complex_or_real(z: Cx) -> Array {
12143    if z[1] == 0.0 {
12144        return Array::scalar_f64(z[0]);
12145    }
12146    Array::new(vec![], Data::Complex(vec![z].into()))
12147}
12148
12149/// `p. y`: the roots of the polynomial whose ascending coefficients y holds,
12150/// as `multiplier ; roots`; a y already in that form converts back to
12151/// coefficients.
12152fn poly_roots(y: &Array, span: Span) -> Result<Array> {
12153    if let Some(parts) = y.as_boxes().filter(|p| !p.is_empty()) {
12154        return Ok(complex_or_real(root_form_coeffs(parts, span)?));
12155    }
12156    let mut c = poly_coeffs_relaxed(y, span)?;
12157    while c.len() > 1 && c[c.len() - 1] == cx::ZERO {
12158        c.pop();
12159    }
12160    // The ZERO polynomial has no leading coefficient to divide by and every
12161    // number for a root: J answers `0 ; ''`, a zero multiplier and no roots
12162    // at all. Only a non-zero constant has no root form.
12163    if c.iter().all(|&k| k == cx::ZERO) {
12164        let pair = vec![Array::scalar_i64(0), Array::new(vec![0], Data::empty(DType::I64))];
12165        return Ok(Array::new(vec![2], Data::Box(pair.into())));
12166    }
12167    if c.len() < 2 {
12168        return Err(Error::domain("a polynomial's roots need a coefficient of x", span));
12169    }
12170    let lead = c[c.len() - 1];
12171    let monic: Vec<Cx> = c.iter().map(|&k| cx::div(k, lead)).collect();
12172    let roots = durand_kerner(&monic);
12173    let pair = vec![scalar_complex_or_real(lead), complex_or_real(roots)];
12174    Ok(Array::new(vec![2], Data::Box(pair.into())))
12175}
12176
12177/// The roots of a monic polynomial, by the Durand–Kerner iteration: every
12178/// root is refined against all the others at once, from spread-out starting
12179/// points, until none of them moves.
12180///
12181/// The answer is ordered by descending real part, then descending
12182/// imaginary part, which is a stable order the iteration itself has none of.
12183fn durand_kerner(monic: &[Cx]) -> Vec<Cx> {
12184    let d = monic.len() - 1;
12185    let seed = [0.4, 0.9];
12186    let mut z: Vec<Cx> = Vec::with_capacity(d);
12187    let mut p = cx::ONE;
12188    for _ in 0..d {
12189        z.push(p);
12190        p = cx::mul(p, seed);
12191    }
12192    let value = |monic: &[Cx], at: Cx| {
12193        let mut v = cx::ZERO;
12194        for &k in monic.iter().rev() {
12195            v = cx::add(cx::mul(v, at), k);
12196        }
12197        v
12198    };
12199    for _ in 0..500 {
12200        let mut moved: f64 = 0.0;
12201        for i in 0..d {
12202            let mut denom = cx::ONE;
12203            for j in 0..d {
12204                if i != j {
12205                    denom = cx::mul(denom, cx::sub(z[i], z[j]));
12206                }
12207            }
12208            if denom == cx::ZERO {
12209                continue;
12210            }
12211            let step = cx::div(value(monic, z[i]), denom);
12212            z[i] = cx::sub(z[i], step);
12213                moved = moved.max(step[0].hypot(step[1]));
12214        }
12215        if moved < 1e-15 {
12216            break;
12217        }
12218    }
12219    let mut z = polished_repeats(monic, z);
12220    // A root within rounding of the real axis is a real root.
12221    for r in &mut z {
12222        if r[1].abs() < 1e-9 {
12223            r[1] = 0.0;
12224        }
12225        if r[0].abs() < 1e-12 {
12226            r[0] = 0.0;
12227        }
12228    }
12229    // Order is the one J answers in: the largest magnitude first, then the
12230    // largest real part, then the largest imaginary part — so ¯3 comes
12231    // before 2, and a conjugate pair keeps the positive half in front. The
12232    // keys are coarsened first, because two members of a pair agree only
12233    // to rounding and the sort needs a total order to stand on.
12234    let coarse = |v: f64| -> f64 {
12235        if v == 0.0 || !v.is_finite() { v } else { format!("{v:.11e}").parse().unwrap_or(v) }
12236    };
12237    let mut keyed: Vec<([f64; 3], Cx)> =
12238        z.into_iter().map(|r| ([coarse(cx::abs(r)), coarse(r[0]), coarse(r[1])], r)).collect();
12239    keyed.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
12240    keyed.into_iter().map(|(_, r)| r).collect()
12241}
12242
12243/// A repeated root, put back where it belongs.
12244///
12245/// Durand–Kerner reaches a root of multiplicity m only to about the m-th
12246/// root of the machine epsilon, so a double root of `1 2 1` comes out as
12247/// two complex values 1e¯8 either side of ¯1: complex noise where the
12248/// answer is a pair of exact reals. The straddle is symmetric, so the
12249/// group's CENTRE carries the accuracy its members lack. Roots within reach
12250/// of one another are gathered and every member of a group moves to the
12251/// group's centre.
12252///
12253/// Reach is a guess, and a wrong one merges two roots that are merely
12254/// close. So the answer is kept only when the polynomial rebuilt from it
12255/// fits the coefficients at least as well as the raw roots do, and the
12256/// widest reach that passes that test is the one taken.
12257fn polished_repeats(monic: &[Cx], z: Vec<Cx>) -> Vec<Cx> {
12258    let d = z.len();
12259    if d < 2 {
12260        return z;
12261    }
12262    let raw = coefficient_error(monic, &z);
12263    let scale = monic.iter().map(|&k| cx::abs(k)).fold(1.0f64, f64::max);
12264    let allowed = raw.max(1e-13 * scale);
12265    for reach in [1e-3, 1e-4, 1e-5, 1e-6, 1e-7] {
12266        // Single linkage: a chain of near neighbours is one group, which
12267        // is what a triple root's three points around the true value are.
12268        let mut group: Vec<usize> = (0..d).collect();
12269        for i in 0..d {
12270            for j in 0..i {
12271                let apart = cx::abs(cx::sub(z[i], z[j]));
12272                let span = reach * (1.0 + cx::abs(z[i]).max(cx::abs(z[j])));
12273                if apart <= span {
12274                    let (a, b) = (group[i], group[j]);
12275                    let (keep, drop) = (a.min(b), a.max(b));
12276                    for g in &mut group {
12277                        if *g == drop {
12278                            *g = keep;
12279                        }
12280                    }
12281                }
12282            }
12283        }
12284        let mut centre = vec![cx::ZERO; d];
12285        let mut size = vec![0usize; d];
12286        for i in 0..d {
12287            centre[group[i]] = cx::add(centre[group[i]], z[i]);
12288            size[group[i]] += 1;
12289        }
12290        if size.iter().all(|&n| n < 2) {
12291            return z;
12292        }
12293        let mut settled: Vec<Option<Cx>> = vec![None; d];
12294        for g in 0..d {
12295            if size[g] == 0 {
12296                continue;
12297            }
12298            let start = cx::div(centre[g], cx::from_real(size[g] as f64));
12299            // Near a root of multiplicity m the polynomial's own value is
12300            // lost to cancellation — it reads as zero over a whole ball —
12301            // so refining against it can go no further. The m-1st
12302            // DERIVATIVE has the same root simply, with none of that
12303            // cancellation, and Newton on it lands exactly: `1 3 3 1`'s
12304            // second derivative is `6 6`, whose one root is ¯1.
12305            settled[g] = Some(if size[g] < 2 {
12306                start
12307            } else {
12308                newton_at(&nth_derivative(monic, size[g] - 1), start)
12309            });
12310        }
12311        let out: Vec<Cx> = (0..d).map(|i| settled[group[i]].unwrap_or(z[i])).collect();
12312        if out.iter().all(|r| r[0].is_finite() && r[1].is_finite())
12313            && coefficient_error(monic, &out) <= allowed
12314        {
12315            return out;
12316        }
12317    }
12318    z
12319}
12320
12321/// Newton's method from `start`, on the coefficients as given.
12322fn newton_at(poly: &[Cx], start: Cx) -> Cx {
12323    let mut z = start;
12324    for _ in 0..40 {
12325        let (mut p, mut slope) = (cx::ZERO, cx::ZERO);
12326        for &k in poly.iter().rev() {
12327            slope = cx::add(cx::mul(slope, z), p);
12328            p = cx::add(cx::mul(p, z), k);
12329        }
12330        if slope == cx::ZERO {
12331            break;
12332        }
12333        let step = cx::div(p, slope);
12334        let next = cx::sub(z, step);
12335        if !next[0].is_finite() || !next[1].is_finite() {
12336            break;
12337        }
12338        z = next;
12339        if cx::abs(step) <= 1e-17 * (1.0 + cx::abs(z)) {
12340            break;
12341        }
12342    }
12343    z
12344}
12345
12346/// The `k`-th derivative of a polynomial's ascending coefficients.
12347fn nth_derivative(c: &[Cx], k: usize) -> Vec<Cx> {
12348    let mut out = c.to_vec();
12349    for _ in 0..k {
12350        if out.len() < 2 {
12351            return vec![cx::ZERO];
12352        }
12353        out = out
12354            .iter()
12355            .enumerate()
12356            .skip(1)
12357            .map(|(i, &v)| cx::mul(v, cx::from_real(i as f64)))
12358            .collect();
12359    }
12360    out
12361}
12362
12363/// How far the monic polynomial rebuilt from `roots` sits from the one the
12364/// coefficients describe: the largest coefficient difference, relative to
12365/// the coefficient it belongs to.
12366fn coefficient_error(monic: &[Cx], roots: &[Cx]) -> f64 {
12367    let mut built = vec![cx::ONE];
12368    for &r in roots {
12369        let mut next = vec![cx::ZERO; built.len() + 1];
12370        for (k, &c) in built.iter().enumerate() {
12371            next[k + 1] = cx::add(next[k + 1], c);
12372            next[k] = cx::sub(next[k], cx::mul(c, r));
12373        }
12374        built = next;
12375    }
12376    let mut worst: f64 = 0.0;
12377    for (k, &want) in monic.iter().enumerate() {
12378        let got = built.get(k).copied().unwrap_or(cx::ZERO);
12379        worst = worst.max(cx::abs(cx::sub(got, want)) / (1.0 + cx::abs(want)));
12380    }
12381    worst
12382}
12383
12384/// `p.. y`: the derivative of the polynomial y's ascending coefficients
12385/// describe, again as coefficients.
12386fn poly_deriv(y: &Array, span: Span) -> Result<Array> {
12387    // A boxed argument is the root form, differentiated through the
12388    // coefficients it stands for: `p.. (<1 2 3)` is `11 _12 3`.
12389    let c = match y.as_boxes().filter(|p| !p.is_empty()) {
12390        Some(parts) => root_form_coeffs(parts, span)?,
12391        None => poly_coeffs_relaxed(y, span)?,
12392    };
12393    if c.len() < 2 {
12394        return Ok(Array::from_i64(vec![0]));
12395    }
12396    let out: Vec<Cx> =
12397        c.iter().enumerate().skip(1).map(|(k, &v)| cx::mul(v, cx::from_real(k as f64))).collect();
12398    Ok(narrow_numbers(complex_or_real(out)))
12399}
12400
12401/// `x p.. y`: the integral of y's coefficients, with x as the constant term.
12402fn poly_integral(x: &Array, y: &Array, span: Span) -> Result<Array> {
12403    // A boxed argument is the root form here too. What it does NOT take is
12404    // an empty of another type: `1 p.. (0$'a')` is a domain error where
12405    // `p.. (0$'a')` answers, and the oracle's line is the line.
12406    let c = match y.as_boxes().filter(|p| !p.is_empty()) {
12407        Some(parts) => root_form_coeffs(parts, span)?,
12408        None => poly_coeffs(y, span)?,
12409    };
12410    let k = poly_coeffs(x, span)?;
12411    let mut out = vec![k.first().copied().unwrap_or(cx::ZERO)];
12412    for (i, &v) in c.iter().enumerate() {
12413        out.push(cx::div(v, cx::from_real((i + 1) as f64)));
12414    }
12415    Ok(narrow_numbers(complex_or_real(out)))
12416}
12417
12418/// A float array whose values are all whole, as integers. Polynomial
12419/// coefficients are computed in floats and mostly come out whole; J prints
12420/// and types them as integers, so libjay narrows them back.
12421fn narrow_numbers(a: Array) -> Array {
12422    let Data::F64(v) = &a.data else { return a };
12423    if v.iter().any(|x| !x.is_finite() || x.fract() != 0.0 || x.abs() > 9e15) {
12424        return a;
12425    }
12426    let values: Vec<i64> = v.iter().map(|&x| x as i64).collect();
12427    Array::new(a.shape, Data::I64(values.into()))
12428}
12429
12430/// `u b. n`: what u is, rather than what it does. Only `0`, the three
12431/// ranks, is answered; the rest of J's characteristics reach into the
12432/// representation of a verb, which libjay does not publish.
12433fn characteristics(u: &Verb, y: &Array, span: Span) -> Result<Array> {
12434    let which = y.to_i64_vec().and_then(|v| v.first().copied());
12435    let chars = |s: String| Ok(Array::from_chars(s.chars().collect()));
12436    match which {
12437        Some(0) => {
12438            let ranks = u.ranks();
12439            Ok(Array::from_f64(
12440                ranks
12441                    .iter()
12442                    .map(|&r| if r == RANK_INF { f64::INFINITY } else { r as f64 })
12443                    .collect(),
12444            ))
12445        }
12446        // `u b. _1` and `u b. 1` answer with a spelling, not a verb: the
12447        // obverse, and the verb that yields the identity element of a
12448        // reduction over no items.
12449        Some(-1) => match obverse(u) {
12450            Some(v) => chars(v.name()),
12451            None => Err(Error::not_yet(
12452                format!("the obverse of {} (no inverse is known)", u.name()),
12453                span,
12454            )),
12455        },
12456        // `b.` is J's conjunction and has no APL spelling, so the identity
12457        // asked for here is always J's.
12458        Some(1) => match reduce_identity(u, 1, crate::Lang::J).as_ref().map(identity_spelling) {
12459            Some(s) => chars(s),
12460            None => Err(Error::not_yet(
12461                format!("the identity function of {} (u b. 1)", u.name()),
12462                span,
12463            )),
12464        },
12465        _ => Err(Error::not_yet("a verb characteristic other than 0, 1 and _1", span)),
12466    }
12467}
12468
12469/// J spells an identity function as the neutral cell reshaped to the frame
12470/// of the argument: `+ b. 1` is `0 $~ }.@$`.
12471fn identity_spelling(d: &Data) -> String {
12472    let one = Array::new(Vec::new(), d.slice(0, 1));
12473    let text = crate::fmt::format_array(&one, &crate::fmt::FmtOpts::J);
12474    format!("{} $~ }}.@$", text.trim())
12475}
12476
12477/// Run `f` with `⍺⍺` and `⍵⍵` naming the operands a user-written operator
12478/// was given, and with whatever they named before put back afterwards.
12479///
12480/// An operand that is an array is bound as a NAME rather than as a verb,
12481/// which is how the body's `⍺⍺` reads as a value. Both slots are saved and
12482/// restored, so an operator applied inside another operator's body leaves
12483/// the outer names as it found them.
12484fn with_operands<R>(
12485    alpha: &Operand,
12486    omega: Option<&Operand>,
12487    ctx: &mut Ctx<'_>,
12488    f: impl FnOnce(&mut Ctx<'_>) -> Result<R>,
12489) -> Result<R> {
12490    let names = ["⍺⍺", "⍵⍵"];
12491    let operands = [Some(alpha), omega];
12492    let saved: Vec<(Option<Verb>, Option<Array>)> =
12493        names.iter().map(|n| (ctx.env.verb(n).cloned(), ctx.env.global(n))).collect();
12494    for (name, operand) in names.iter().zip(operands) {
12495        match operand {
12496            Some(Operand::Func(v)) => ctx.env.define((*name).to_string(), (**v).clone()),
12497            Some(Operand::Value(a)) => ctx.env.set_global((*name).to_string(), (**a).clone()),
12498            None => {}
12499        }
12500    }
12501    let out = f(ctx);
12502    for (name, (verb, value)) in names.iter().zip(saved) {
12503        match verb {
12504            Some(v) => ctx.env.define((*name).to_string(), v),
12505            None => ctx.env.undefine(name),
12506        }
12507        match value {
12508            Some(a) => ctx.env.set_global((*name).to_string(), a),
12509            None => ctx.env.unset_global(name),
12510        }
12511    }
12512    out
12513}
12514
12515/// True for APL's MIXED SIMPLE array: every element is a simple scalar,
12516/// and no one type holds all of them. libjay keeps such an array as boxed
12517/// scalars, but its depth is 1 and nothing may open it further.
12518fn is_mixed_simple(a: &Array) -> bool {
12519    let Some(items) = a.as_boxes() else { return false };
12520    if items.is_empty() || items.iter().any(|b| b.rank() != 0 || b.dtype() == DType::Box) {
12521        return false;
12522    }
12523    let mut common = Some(items[0].dtype());
12524    for b in &items[1..] {
12525        common = common.and_then(|t| DType::promote(t, b.dtype()));
12526    }
12527    common.is_none()
12528}
12529
12530/// APL `⊆ y` (Dyalog): nest — y enclosed, unless it already is nested or
12531/// is a simple scalar, neither of which enclosing changes.
12532fn nest(y: &Array) -> Array {
12533    if y.dtype() == DType::Box || y.rank() == 0 {
12534        return y.clone();
12535    }
12536    Array::boxed(y.clone())
12537}
12538
12539/// APL `f⌸ y` and `x f⌸ y` (Dyalog's key): the distinct major cells of the
12540/// left argument, in first-occurrence order, each paired with what shares
12541/// it — the positions it occupies, or the right argument's items there.
12542fn key_pairs(
12543    u: &Verb,
12544    keys: &Array,
12545    values: Option<&Array>,
12546    ctx: &mut Ctx<'_>,
12547    span: Span,
12548) -> Result<Array> {
12549    let base = if keys.rank() == 0 { Array::new(vec![1], keys.data.clone()) } else { keys.clone() };
12550    let n = base.items();
12551    if let Some(v) = values && v.items() != n {
12552        return Err(Error::new(
12553            ErrorKind::Length,
12554            format!("{n} key(s) for {} item(s)", v.items()),
12555            Some(span),
12556        ));
12557    }
12558    let groups = group_positions(&base, ctx.cfg.tol);
12559    let origin = ctx.cfg.rules.origin;
12560    let mut cells = Vec::with_capacity(groups.len());
12561    for (first, at) in &groups {
12562        let key = item_or_self(&base, *first);
12563        let group = match values {
12564            Some(v) => select_items(v, at),
12565            None => Array::from_i64(at.iter().map(|&i| origin + i as i64).collect()),
12566        };
12567        // A dfn that never names `⍺` has no dyadic valence; the key is
12568        // then of no use to it and the group is all it is given.
12569        let monadic = matches!(u, Verb::Explicit(d) if d.left.is_none());
12570        cells.push(if monadic {
12571            u.monad(&group, ctx, span)?
12572        } else {
12573            u.dyad(&key, &group, ctx, span)?
12574        });
12575    }
12576    let count = cells.len();
12577    assemble(&[count], cells, span)
12578}
12579
12580/// The distinct items of `y`, each as (its first position, every position
12581/// it holds), in first-occurrence order.
12582fn group_positions(y: &Array, tol: Tol) -> Vec<(usize, Vec<usize>)> {
12583    let n = y.items();
12584    let m = y.item_size();
12585    // Exact equality is an equivalence a hash stands in for, so the groups
12586    // come out of one pass. Tolerant equality is not one, and neither a box
12587    // nor an exact number has a cheap key: those are compared by content,
12588    // each item against the distinct ones already found.
12589    let hashable = match y.dtype() {
12590        DType::Box | DType::Ext | DType::Rat => false,
12591        DType::F64 | DType::Complex => tol.ct == 0.0,
12592        _ => true,
12593    };
12594    if hashable {
12595        return if m == 1 {
12596            group_by_key(n, |i| elem_key(&y.data, i))
12597        } else {
12598            group_by_key(n, |i| (0..m).map(|k| elem_key(&y.data, i * m + k)).collect::<Vec<u64>>())
12599        };
12600    }
12601    let mut keys: Vec<Array> = Vec::new();
12602    let mut groups: Vec<(usize, Vec<usize>)> = Vec::new();
12603    for i in 0..n {
12604        let item = y.item(i);
12605        match keys.iter().position(|k| arrays_match(k, &item, tol)) {
12606            Some(at) => groups[at].1.push(i),
12607            None => {
12608                keys.push(item);
12609                groups.push((i, vec![i]));
12610            }
12611        }
12612    }
12613    groups
12614}
12615
12616/// The positions `0 .. n`, grouped by the key each of them has, in the
12617/// order the keys first appear: one hash lookup per position, not one
12618/// comparison per position per group.
12619fn group_by_key<K, F>(n: usize, key: F) -> Vec<(usize, Vec<usize>)>
12620where
12621    K: Eq + std::hash::Hash,
12622    F: Fn(usize) -> K,
12623{
12624    use std::collections::hash_map::Entry;
12625    let mut groups: Vec<(usize, Vec<usize>)> = Vec::new();
12626    let mut at: HashMap<K, usize, KeyHash> =
12627        HashMap::with_capacity_and_hasher(n.min(1 << 16), KeyHash);
12628    for i in 0..n {
12629        match at.entry(key(i)) {
12630            Entry::Occupied(e) => groups[*e.get()].1.push(i),
12631            Entry::Vacant(e) => {
12632                e.insert(groups.len());
12633                groups.push((i, vec![i]));
12634            }
12635        }
12636    }
12637    groups
12638}
12639
12640/// The hasher the grouping uses. Its keys are [`elem_key`] values, which
12641/// already spread a value across the whole of a `u64`, so mixing them costs
12642/// a multiply where the default hasher runs a block cipher over them.
12643/// Nothing here is exposed to a chosen key, which is what that default is
12644/// for.
12645#[derive(Clone, Copy, Default)]
12646struct KeyHash;
12647
12648impl std::hash::BuildHasher for KeyHash {
12649    type Hasher = KeyHasher;
12650    fn build_hasher(&self) -> KeyHasher {
12651        KeyHasher(0)
12652    }
12653}
12654
12655struct KeyHasher(u64);
12656
12657impl std::hash::Hasher for KeyHasher {
12658    fn finish(&self) -> u64 {
12659        let mut x = self.0;
12660        x ^= x >> 33;
12661        x = x.wrapping_mul(0xff51_afd7_ed55_8ccd);
12662        x ^ (x >> 29)
12663    }
12664    fn write(&mut self, bytes: &[u8]) {
12665        for &b in bytes {
12666            self.write_u64(b as u64);
12667        }
12668    }
12669    fn write_u64(&mut self, n: u64) {
12670        self.0 = (self.0.rotate_left(5) ^ n).wrapping_mul(0x9e37_79b9_7f4a_7c15);
12671    }
12672    fn write_usize(&mut self, n: usize) {
12673        self.write_u64(n as u64);
12674    }
12675}
12676
12677/// APL `x ⍕ y`: format by specification. `x` is one width-and-precision
12678/// pair per column of y's last axis, one pair for all of them, or a lone
12679/// precision, which takes the width the values need plus a separating
12680/// blank. A value that does not fit its width is a domain error, as the
12681/// reference has it.
12682fn format_spec(x: &Array, y: &Array, fmt: &FmtOpts, span: Span) -> Result<Array> {
12683    let spec = x
12684        .to_i64_vec()
12685        .ok_or_else(|| Error::domain("a format specification is whole numbers", span))?;
12686    if y.dtype() == DType::Box {
12687        return Err(Error::not_yet("format by specification of a nested array", span));
12688    }
12689    let cols = if y.rank() == 0 { 1 } else { y.shape[y.rank() - 1] };
12690    let rows = y.count() / cols.max(1);
12691    // One number is a precision alone; pairs are width and precision.
12692    let pairs: Vec<(Option<i64>, i64)> = match spec.len() {
12693        1 => vec![(None, spec[0]); cols],
12694        2 => vec![(Some(spec[0]), spec[1]); cols],
12695        n if n == 2 * cols => spec.chunks(2).map(|c| (Some(c[0]), c[1])).collect(),
12696        n => {
12697            return Err(Error::new(
12698                ErrorKind::Length,
12699                format!("{n} specification value(s) for {cols} column(s)"),
12700                Some(span),
12701            ));
12702        }
12703    };
12704    if pairs.iter().any(|&(w, p)| w.is_some_and(|w| w < 0) || p < 0) {
12705        return Err(Error::domain("a format width and precision are nonnegative", span));
12706    }
12707    // A width and a precision are lengths, and a written number is free to
12708    // ask for more characters than any machine holds. The ceiling applies
12709    // here as it does to a shape.
12710    for &(w, p) in &pairs {
12711        crate::limits::count(w.unwrap_or(0) as u128, span)?;
12712        crate::limits::count(p as u128, span)?;
12713    }
12714    let numbers = y.to_f64_vec();
12715    let text = |i: usize, p: i64| -> String {
12716        match (&y.data, &numbers) {
12717            (Data::Char(v), _) => v[i].to_string(),
12718            (_, Some(v)) => {
12719                let s = format!("{:.*}", p as usize, v[i]);
12720                if v[i] < 0.0 { format!("{}{}", fmt.neg, &s[1..]) } else { s }
12721            }
12722            _ => String::new(),
12723        }
12724    };
12725    if y.dtype() != DType::Char && numbers.is_none() {
12726        return Err(Error::domain("format by specification takes numbers or characters", span));
12727    }
12728    // A width the caller did not give is the widest value plus a blank.
12729    let widths: Vec<usize> = pairs
12730        .iter()
12731        .enumerate()
12732        .map(|(c, &(w, p))| match w {
12733            Some(w) => w as usize,
12734            None => {
12735                (0..rows).map(|r| text(r * cols + c, p).chars().count()).max().unwrap_or(0) + 1
12736            }
12737        })
12738        .collect();
12739    let line = crate::limits::count(widths.iter().map(|&w| w as u128).sum(), span)?;
12740    let total = crate::limits::count(rows as u128 * line as u128, span)?;
12741    let mut out: Vec<char> = Vec::with_capacity(total);
12742    for r in 0..rows {
12743        for c in 0..cols {
12744            let s = text(r * cols + c, pairs[c].1);
12745            let len = s.chars().count();
12746            if len > widths[c] {
12747                return Err(Error::domain(
12748                    format!("{s} does not fit a field {} wide", widths[c]),
12749                    span,
12750                ));
12751            }
12752            out.extend(std::iter::repeat_n(' ', widths[c] - len));
12753            out.extend(s.chars());
12754        }
12755    }
12756    let mut shape = if y.rank() == 0 { Vec::new() } else { y.shape[..y.rank() - 1].to_vec() };
12757    shape.push(line);
12758    Ok(Array::new(shape, Data::Char(out.into())))
12759}
12760
12761/// J `x ;: y`: the sequential machine.
12762///
12763/// x is the boxed description `f ; s ; m ; ijrd`, of which `m` and `ijrd`
12764/// may be left off. `s` is the transition table, shaped `p q 2`: at state
12765/// `r` and input class `c`, `s[r;c;0]` is the state to go to and
12766/// `s[r;c;1]` the output code — 0 nothing, 1 start a word here, 2 end a
12767/// word and start another, 3 end a word, 6 stop. `m` maps an input element
12768/// to its class, indexed by the character's codepoint; with none, a
12769/// numeric argument IS the classes. `ijrd` is the starting position, the
12770/// starting word (`_1` for none), the starting state and what to do with
12771/// the end of the input: a class to make one last transition with, or `_1`
12772/// to end the word in hand. `f` picks the answer: 0 the boxed words, 1
12773/// their elements catenated, 2 each word's position and length, 3 the
12774/// table position that ended it, 4 both, 5 the whole trace.
12775fn sequential_machine(x: &Array, y: &Array, span: Span) -> Result<Array> {
12776    let Some(parts) = x.as_boxes() else {
12777        return Err(Error::domain("a sequential machine is a boxed description", span));
12778    };
12779    if x.rank() > 1 || !(2..=4).contains(&parts.len()) {
12780        return Err(Error::domain(
12781            "a sequential machine is 2 to 4 boxes: f ; s ; m ; ijrd",
12782            span,
12783        ));
12784    }
12785    let whole = |a: &Array, what: &str| -> Result<Vec<i64>> {
12786        a.to_i64_vec().ok_or_else(|| Error::domain(format!("{what} is whole numbers"), span))
12787    };
12788    let form = *whole(&parts[0], "a sequential machine's result form")?
12789        .first()
12790        .ok_or_else(|| Error::domain("a sequential machine needs a result form", span))?;
12791    if !(0..=5).contains(&form) {
12792        return Err(Error::domain(format!("{form} is not a result form of 0 to 5"), span));
12793    }
12794    let table = &parts[1];
12795    if table.rank() != 3 || table.shape[2] != 2 {
12796        return Err(Error::new(
12797            ErrorKind::Rank,
12798            "a sequential machine's transition table is shaped p q 2",
12799            Some(span),
12800        ));
12801    }
12802    let (states, classes) = (table.shape[0], table.shape[1]);
12803    let entries = whole(table, "a transition table")?;
12804    let map = parts.get(2).filter(|a| a.count() > 0);
12805    let start = match parts.get(3) {
12806        Some(a) => whole(a, "a sequential machine's starting values")?,
12807        None => Vec::new(),
12808    };
12809    let start = if start.is_empty() { vec![0, -1, 0, -1] } else { start };
12810    if start.len() != 4 {
12811        return Err(Error::new(
12812            ErrorKind::Length,
12813            "a sequential machine starts from four values: i j r d",
12814            Some(span),
12815        ));
12816    }
12817    let (mut i, mut word, mut state, ending) = (start[0], start[1], start[2], start[3]);
12818    let n = y.count() as i64;
12819
12820    // The class of the element at `at`: read through the map where there
12821    // is one, and the element itself where there is not.
12822    let codes: Option<Vec<i64>> = match map {
12823        Some(m) => Some(whole(m, "a sequential machine's map")?),
12824        None => None,
12825    };
12826    let values: Vec<i64> = match (&y.data, &codes) {
12827        (Data::Char(v), Some(_)) => v.as_slice().iter().map(|&c| c as i64).collect(),
12828        (_, None) => y
12829            .to_i64_vec()
12830            .ok_or_else(|| Error::domain("a sequential machine over characters needs a map", span))?,
12831        _ => {
12832            return Err(Error::not_yet(
12833                "a sequential machine's map over a numeric argument (x's third box)",
12834                span,
12835            ));
12836        }
12837    };
12838    let class_at = |at: i64| -> Result<i64> {
12839        let raw = values[at as usize];
12840        let Some(m) = &codes else { return Ok(raw) };
12841        if raw < 0 || raw as usize >= m.len() {
12842            return Err(Error::new(
12843                ErrorKind::Domain,
12844                format!("{raw} is outside a map of {} entries", m.len()),
12845                Some(span),
12846            ));
12847        }
12848        Ok(m[raw as usize])
12849    };
12850
12851    let mut trace: Vec<i64> = Vec::new();
12852    let mut words: Vec<(i64, i64, i64)> = Vec::new();
12853    let mut emit = |word: i64, at: i64, place: i64| -> Result<()> {
12854        if word < 0 {
12855            return Err(Error::new(
12856                ErrorKind::Domain,
12857                "a sequential machine ended a word before one had begun",
12858                Some(span),
12859            ));
12860        }
12861        words.push((word, at - word, place));
12862        Ok(())
12863    };
12864    loop {
12865        let class = if i < n {
12866            class_at(i)?
12867        } else if ending >= 0 {
12868            ending
12869        } else {
12870            // The input is spent and the end asks for no transition: what
12871            // is in hand is the last word. The reference gives it the table
12872            // position class 0 in the state reached would have.
12873            if word >= 0 {
12874                emit(word, i, classes as i64 * state)?;
12875            }
12876            break;
12877        };
12878        if state < 0 || state as usize >= states || class < 0 || class as usize >= classes {
12879            return Err(Error::new(
12880                ErrorKind::Domain,
12881                format!(
12882                    "state {state} and class {class} are outside a {states} by {classes} table"
12883                ),
12884                Some(span),
12885            ));
12886        }
12887        let at = (state as usize * classes + class as usize) * 2;
12888        let (next, code) = (entries[at], entries[at + 1]);
12889        trace.extend_from_slice(&[i, word, state, class, next, code]);
12890        let place = class + classes as i64 * state;
12891        state = next;
12892        match code {
12893            0 => {}
12894            1 => word = i,
12895            2 => {
12896                emit(word, i, place)?;
12897                word = i;
12898            }
12899            3 => {
12900                emit(word, i, place)?;
12901                word = -1;
12902            }
12903            4 | 5 => {
12904                return Err(Error::not_yet(
12905                    "a sequential machine's vector output (codes 4 and 5)",
12906                    span,
12907                ));
12908            }
12909            6 => break,
12910            other => {
12911                return Err(Error::domain(
12912                    format!("{other} is not a sequential machine output code"),
12913                    span,
12914                ));
12915            }
12916        }
12917        if i >= n {
12918            break;
12919        }
12920        i += 1;
12921    }
12922    Ok(sequential_result(form, &words, &trace, y))
12923}
12924
12925/// The answer a sequential machine's result form asks for, out of the words
12926/// it marked off and the trace it left.
12927fn sequential_result(form: i64, words: &[(i64, i64, i64)], trace: &[i64], y: &Array) -> Array {
12928    let piece = |&(at, len, _): &(i64, i64, i64)| {
12929        Array::new(vec![len as usize], y.data.slice(at as usize, (at + len) as usize))
12930    };
12931    match form {
12932        0 => Array::new(
12933            vec![words.len()],
12934            Data::Box(words.iter().map(piece).collect::<Vec<_>>().into()),
12935        ),
12936        1 => {
12937            let mut data = Data::empty(y.dtype());
12938            for w in words {
12939                data.extend_from(&piece(w).data);
12940            }
12941            let n = data.len();
12942            Array::new(vec![n], data)
12943        }
12944        2 => Array::new(
12945            vec![words.len(), 2],
12946            Data::I64(words.iter().flat_map(|&(at, len, _)| [at, len]).collect::<Vec<_>>().into()),
12947        ),
12948        3 => Array::from_i64(words.iter().map(|&(_, _, place)| place).collect()),
12949        4 => Array::new(
12950            vec![words.len(), 3],
12951            Data::I64(
12952                words
12953                    .iter()
12954                    .flat_map(|&(at, len, place)| [at, len, place])
12955                    .collect::<Vec<_>>()
12956                    .into(),
12957            ),
12958        ),
12959        _ => Array::new(vec![trace.len() / 6, 6], Data::I64(trace.to_vec().into())),
12960    }
12961}
12962
12963/// J `x ". y`: the numbers the characters of y spell, with x standing in
12964/// for every blank-separated word that is not a number. y arrives as one
12965/// line — the verb's right rank is 1 — so a character matrix is read a row
12966/// at a time and the rows are framed back together.
12967fn parse_numbers(x: &Array, y: &Array, span: Span) -> Result<Array> {
12968    if x.count() != 1 {
12969        return Err(Error::new(
12970            ErrorKind::Rank,
12971            "the stand-in for an unreadable word is one value",
12972            Some(span),
12973        ));
12974    }
12975    // No character to read is no word to read it as, whatever type the
12976    // empty right argument was going to hold: `0.5 ". i.0` is the empty.
12977    if y.count() == 0 {
12978        return Ok(Array::new(y.shape.clone(), Data::empty(DType::Bool)));
12979    }
12980    let Data::Char(text) = &y.data else {
12981        return Err(Error::domain("reading numbers from text needs characters", span));
12982    };
12983    let line: String = text.as_slice().iter().collect();
12984    crate::frontend::j::numbers_from_text(&line, x)
12985        .ok_or_else(|| Error::domain("the stand-in for an unreadable word is a number", span))
12986}
12987
12988/// One field of J's `x ": y`, without its padding: `w j d` says how wide
12989/// the field is and how many digits follow the point, and a NEGATIVE width
12990/// asks for the exponential form instead of the fixed one.
12991fn format_field(value: f64, precision: usize, exponential: bool, neg: char) -> String {
12992    let sign = |s: String| match s.strip_prefix('-') {
12993        // A value that rounds to nothing keeps no sign, as the reference
12994        // has it: `5j2 ": _0.001` is ` 0.00`.
12995        Some(rest) if rest.bytes().all(|b| !b.is_ascii_digit() || b == b'0') => rest.to_string(),
12996        Some(rest) => format!("{neg}{rest}"),
12997        None => s,
12998    };
12999    if !exponential {
13000        return sign(format!("{value:.precision$}"));
13001    }
13002    // `1.500e3`, `1.234e_4`: the mantissa to the asked-for precision, then
13003    // the exponent written as J writes an integer.
13004    let text = format!("{value:.precision$e}");
13005    let (mantissa, exponent) = text.split_once('e').unwrap_or((text.as_str(), "0"));
13006    let exponent = match exponent.strip_prefix('-') {
13007        Some(rest) => format!("{neg}{rest}"),
13008        None => exponent.to_string(),
13009    };
13010    format!("{}e{exponent}", sign(mantissa.to_string()))
13011}
13012
13013/// J `x ": y`: format by specification.
13014///
13015/// x is one complex `w j d` per column of y's last axis, or one for all of
13016/// them: `w` is the field width and `d` the digits after the point. A width
13017/// of zero takes whatever the column needs, with a blank between it and the
13018/// column before. A value too wide for its field is written as asterisks
13019/// rather than refused, which is what the reference does.
13020fn format_spec_j(x: &Array, y: &Array, fmt: &FmtOpts, span: Span) -> Result<Array> {
13021    let Some(spec) = x.to_complex_vec() else {
13022        return Err(Error::domain("a format specification is numbers", span));
13023    };
13024    if y.dtype() == DType::Box {
13025        return Err(Error::domain("format by specification takes numbers", span));
13026    }
13027    let Some(values) = y.to_f64_vec() else {
13028        return Err(Error::domain("format by specification takes numbers", span));
13029    };
13030    let cols = if y.rank() == 0 { 1 } else { y.shape[y.rank() - 1] };
13031    let rows = if cols == 0 { 0 } else { y.count() / cols };
13032    let fields: Vec<[f64; 2]> = match spec.len() {
13033        1 => vec![spec[0]; cols],
13034        n if n == cols => spec,
13035        n => {
13036            return Err(Error::new(
13037                ErrorKind::Length,
13038                format!("{n} specification value(s) for {cols} column(s)"),
13039                Some(span),
13040            ));
13041        }
13042    };
13043    // A width and a digit count are lengths, and a written number is free
13044    // to ask for more characters than any machine holds. The ceiling
13045    // applies here as it does to a shape: refuse the request instead of
13046    // handing the product to an allocator.
13047    for &[w, d] in &fields {
13048        crate::limits::count(w.abs() as u128, span)?;
13049        crate::limits::count(d.max(0.0) as u128, span)?;
13050    }
13051    let text = |r: usize, c: usize| {
13052        let [w, d] = fields[c];
13053        // Only a column of automatic width renders every digit asked for.
13054        // Where the width is given, a digit count that reaches it already
13055        // overflows the field — the point and the digits alone are wider —
13056        // so rendering past that point cannot change the answer.
13057        let digits = if w == 0.0 { d.max(0.0) } else { d.max(0.0).min(w.abs()) };
13058        format_field(values[r * cols + c], digits as usize, w < 0.0, fmt.neg)
13059    };
13060    // A width of zero is the widest value in the column, and a blank
13061    // between it and whatever stands to its left.
13062    let widths: Vec<usize> = (0..cols)
13063        .map(|c| {
13064            let w = fields[c][0];
13065            if w != 0.0 {
13066                return w.abs() as usize;
13067            }
13068            let wide = (0..rows).map(|r| text(r, c).chars().count()).max().unwrap_or(0);
13069            wide + usize::from(c > 0)
13070        })
13071        .collect();
13072    let line = crate::limits::count(widths.iter().map(|&w| w as u128).sum(), span)?;
13073    let total = crate::limits::count(rows as u128 * line as u128, span)?;
13074    let mut out: Vec<char> = Vec::with_capacity(total);
13075    for r in 0..rows {
13076        for c in 0..cols {
13077            let s = text(r, c);
13078            // The exponential form is written from the LEFT, one column of
13079            // sign in front of it; the fixed one is right-justified.
13080            let (lead, body) = match (fields[c][0] < 0.0, s.strip_prefix(fmt.neg)) {
13081                (false, _) => (String::new(), s.as_str()),
13082                (true, Some(rest)) => (fmt.neg.to_string(), rest),
13083                (true, None) => (" ".to_string(), s.as_str()),
13084            };
13085            let len = lead.chars().count() + body.chars().count();
13086            if len > widths[c] {
13087                out.extend(std::iter::repeat_n('*', widths[c]));
13088                continue;
13089            }
13090            if fields[c][0] < 0.0 {
13091                out.extend(lead.chars());
13092                out.extend(body.chars());
13093                out.extend(std::iter::repeat_n(' ', widths[c] - len));
13094            } else {
13095                out.extend(std::iter::repeat_n(' ', widths[c] - len));
13096                out.extend(body.chars());
13097            }
13098        }
13099    }
13100    let mut shape = if y.rank() == 0 { Vec::new() } else { y.shape[..y.rank() - 1].to_vec() };
13101    shape.push(line);
13102    Ok(Array::new(shape, Data::Char(out.into())))
13103}
13104
13105/// APL `⍳ y`: the indices of an array whose shape is y. One length gives
13106/// the plain counting vector; two or more give an array of that shape whose
13107/// elements are the boxed coordinate vectors.
13108fn iota_apl(y: &Array, origin: i64, near: NearInt, span: Span) -> Result<Array> {
13109    if y.rank() > 1 {
13110        return Err(Error::new(
13111            ErrorKind::Rank,
13112            "the index generator takes a shape, which is a scalar or a vector",
13113            Some(span),
13114        ));
13115    }
13116    let dims = y
13117        .to_i64_vec_near(near)
13118        .ok_or_else(|| Error::domain("index generator needs an integer argument", span))?;
13119    if dims.iter().any(|&n| n < 0) {
13120        return Err(Error::domain("index generator needs nonnegative lengths", span));
13121    }
13122    if dims.len() <= 1 {
13123        let n = dims.first().copied().unwrap_or(0);
13124        crate::limits::count(n as u128, span)?;
13125        return Ok(Array::from_i64((0..n).map(|i| origin + i).collect()));
13126    }
13127    let shape: Vec<usize> = dims.iter().map(|&n| n as usize).collect();
13128    let total = crate::limits::elements(&shape, span)?;
13129    let mut cells = Vec::with_capacity(total);
13130    let mut coord = vec![0usize; shape.len()];
13131    for _ in 0..total {
13132        cells.push(Array::from_i64(coord.iter().map(|&c| origin + c as i64).collect()));
13133        odometer(&mut coord, &shape);
13134    }
13135    Ok(Array::new(shape, Data::Box(cells.into())))
13136}
13137
13138/// J carries an argument's exactness into the verbs that answer with
13139/// counts and digits: `$`, `#`, `#.`, `#:`, `p:` and `q:` of an extended or
13140/// rational argument answer with extended integers, not machine ones. The
13141/// values are the same either way; only the type differs, and J's own
13142/// `3!:0` reports it.
13143fn carry_exact(result: Array, y: &Array) -> Array {
13144    if !matches!(y.dtype(), DType::Ext | DType::Rat) {
13145        return result;
13146    }
13147    match result.data.cast(DType::Ext) {
13148        Some(data) => Array::new(result.shape, data),
13149        None => result,
13150    }
13151}
13152
13153fn carry_exact2(result: Array, x: &Array, y: &Array) -> Array {
13154    let widened = carry_exact(result, x);
13155    carry_exact(widened, y)
13156}
13157
13158/// `m b.`: one of the sixteen boolean functions of two bits, and — sixteen
13159/// higher — the same function applied to every bit of a pair of integers.
13160fn truth_table(m: u8, x: &Array, y: &Array, span: Span) -> Result<Array> {
13161    let table = m & 15;
13162    let bit = |a: i64, b: i64| ((table >> (3 - (2 * a + b))) & 1) as i64;
13163    let xs = x
13164        .to_i64_vec()
13165        .ok_or_else(|| Error::domain("a boolean function takes integers", span))?;
13166    let ys = y
13167        .to_i64_vec()
13168        .ok_or_else(|| Error::domain("a boolean function takes integers", span))?;
13169    let (a, b) = (xs.first().copied().unwrap_or(0), ys.first().copied().unwrap_or(0));
13170    if m < 16 {
13171        if !(0..=1).contains(&a) || !(0..=1).contains(&b) {
13172            return Err(Error::domain(
13173                format!("{m} b. takes 0 and 1; {m} b. + 16 is the same function on every bit"),
13174                span,
13175            ));
13176        }
13177        return Ok(Array::scalar_bool(bit(a, b) != 0));
13178    }
13179    let mut out = 0i64;
13180    for k in 0..64 {
13181        if bit((a >> k) & 1, (b >> k) & 1) != 0 {
13182            out |= 1i64 << k;
13183        }
13184    }
13185    Ok(Array::scalar_i64(out))
13186}
13187
13188/// APL `A[i;j]←v`: `base` with the elements the slots select replaced by
13189/// `value`. An elided slot takes its whole axis; a scalar slot drops its
13190/// axis from the shape the value has to match. The base is copied, so the
13191/// array the name held before is untouched.
13192pub fn amend_at(
13193    base: &Array,
13194    slots: &[Option<Array>],
13195    value: &Array,
13196    origin: i64,
13197    near: NearInt,
13198    span: Span,
13199) -> Result<Array> {
13200    if slots.len() != base.rank() {
13201        return Err(Error::new(
13202            ErrorKind::Rank,
13203            format!(
13204                "indexed assignment needs one index per axis: {} slot(s) for a rank-{} value",
13205                slots.len(),
13206                base.rank()
13207            ),
13208            Some(span),
13209        ));
13210    }
13211    // The positions below are row-major offsets into both buffers, so a
13212    // column-major one is laid out before it is read or written.
13213    if !base.is_row_major() || !value.is_row_major() {
13214        let (b, v) = (base.to_row_major(), value.to_row_major());
13215        return amend_at(&b, slots, &v, origin, near, span);
13216    }
13217    // One list of positions per axis, and the shape the value must match.
13218    let mut axes: Vec<Vec<usize>> = Vec::with_capacity(slots.len());
13219    let mut selected: Vec<usize> = Vec::new();
13220    for (k, slot) in slots.iter().enumerate() {
13221        let len = base.shape[k];
13222        let Some(idx) = slot else {
13223            axes.push((0..len).collect());
13224            selected.push(len);
13225            continue;
13226        };
13227        let Some(values) = idx.to_i64_vec_near(near) else {
13228            return Err(Error::new(
13229                ErrorKind::Type,
13230                "an index must be numeric",
13231                Some(span),
13232            ));
13233        };
13234        let mut positions = Vec::with_capacity(values.len());
13235        for v in values {
13236            let p = v - origin;
13237            if p < 0 || p as usize >= len {
13238                return Err(Error::new(
13239                    ErrorKind::Domain,
13240                    format!("index {v} is outside axis {k}, which has {len} element(s)"),
13241                    Some(span),
13242                ));
13243            }
13244            positions.push(p as usize);
13245        }
13246        // A scalar index drops its axis, as it does when reading.
13247        if idx.rank() > 0 {
13248            selected.push(positions.len());
13249        }
13250        axes.push(positions);
13251    }
13252    let count: usize = axes.iter().map(Vec::len).product();
13253    if value.rank() != 0 && (value.shape != selected || value.count() != count) {
13254        return Err(Error::new(
13255            ErrorKind::Shape,
13256            format!(
13257                "indexed assignment needs a scalar or a {} value, not a {} one",
13258                show_shape(&selected),
13259                show_shape(&value.shape)
13260            ),
13261            Some(span),
13262        ));
13263    }
13264    // The two sides meet at the wider type, so assigning a float into an
13265    // integer array widens the array rather than truncating the value.
13266    let dtype = DType::promote(base.dtype(), value.dtype()).ok_or_else(|| {
13267        Error::new(
13268            ErrorKind::Type,
13269            format!(
13270                "cannot put a {} value into a {} array",
13271                value.dtype().name(),
13272                base.dtype().name()
13273            ),
13274            Some(span),
13275        )
13276    })?;
13277    let mut out = base.cast(dtype).ok_or_else(|| Error::internal("promotion failed"))?;
13278    let src = value.cast(dtype).ok_or_else(|| Error::internal("promotion failed"))?;
13279    let strides = row_major_strides(&base.shape);
13280    let mut coords = vec![0usize; axes.len()];
13281    for n in 0..count {
13282        let mut rest = n;
13283        for k in (0..axes.len()).rev() {
13284            let len = axes[k].len();
13285            coords[k] = axes[k][rest % len];
13286            rest /= len;
13287        }
13288        let at: usize = coords.iter().zip(&strides).map(|(c, s)| c * s).sum();
13289        let from = if src.rank() == 0 { 0 } else { n };
13290        put_element(&mut out.data, at, &src.data, from);
13291    }
13292    Ok(out)
13293}
13294
13295fn row_major_strides(shape: &[usize]) -> Vec<usize> {
13296    let mut strides = vec![1usize; shape.len()];
13297    for k in (0..shape.len().saturating_sub(1)).rev() {
13298        strides[k] = strides[k + 1] * shape[k + 1];
13299    }
13300    strides
13301}
13302
13303/// Copy one element between two buffers of the same type.
13304fn put_element(dst: &mut Data, at: usize, src: &Data, from: usize) {
13305    match (dst, src) {
13306        (Data::Bool(d), Data::Bool(s)) => d.to_mut()[at] = s.as_slice()[from],
13307        (Data::I64(d), Data::I64(s)) => d.to_mut()[at] = s.as_slice()[from],
13308        (Data::Ext(d), Data::Ext(s)) => d.to_mut()[at] = s.as_slice()[from].clone(),
13309        (Data::Rat(d), Data::Rat(s)) => d.to_mut()[at] = s.as_slice()[from].clone(),
13310        (Data::F64(d), Data::F64(s)) => d.to_mut()[at] = s.as_slice()[from],
13311        (Data::Char(d), Data::Char(s)) => d.to_mut()[at] = s.as_slice()[from],
13312        (Data::Box(d), Data::Box(s)) => d.to_mut()[at] = s.as_slice()[from].clone(),
13313        // Both sides were cast to one type above.
13314        _ => debug_assert!(false, "amend across types"),
13315    }
13316}
13317
13318/// Which of an agenda's verbs the selector picks. The selector runs at the
13319/// same arguments the agenda was given, and its value must be one index.
13320fn agenda_pick(
13321    vs: &[Verb],
13322    w: &Verb,
13323    x: Option<&Array>,
13324    y: &Array,
13325    ctx: &mut Ctx<'_>,
13326    span: Span,
13327) -> Result<Verb> {
13328    let chosen = match x {
13329        None => w.monad(y, ctx, span)?,
13330        Some(x) => w.dyad(x, y, ctx, span)?,
13331    };
13332    let at = chosen
13333        .to_i64_vec_near(ctx.cfg.near())
13334        .and_then(|v| v.first().copied())
13335        .ok_or_else(|| Error::domain("an agenda index must be an integer", span))?;
13336    pick_gerund(vs, at, span)
13337}
13338
13339/// One verb of a gerund by index, with the diagnostic the out-of-range case
13340/// deserves.
13341pub(crate) fn pick_gerund(vs: &[Verb], at: i64, span: Span) -> Result<Verb> {
13342    usize::try_from(at)
13343        .ok()
13344        .and_then(|k| vs.get(k))
13345        .cloned()
13346        .ok_or_else(|| {
13347            Error::domain(
13348                format!("agenda {at} is out of range: the gerund has {} verbs", vs.len()),
13349                span,
13350            )
13351        })
13352}
13353
13354/// `` m`:0 `` and `` m`:3 ``, the two evoke-gerund forms that are not a
13355/// train. `0` applies every verb of the gerund to the arguments and frames
13356/// the answers; `3` inserts the verbs between the items of y, taking them
13357/// left to right and cycling, and folds right to left as insert does.
13358fn evoke(
13359    vs: &[Verb],
13360    form: i64,
13361    x: Option<&Array>,
13362    y: &Array,
13363    ctx: &mut Ctx<'_>,
13364    span: Span,
13365) -> Result<Array> {
13366    if vs.is_empty() {
13367        return Err(Error::domain("an evoked gerund is empty", span));
13368    }
13369    if form == 0 {
13370        let mut cells = Vec::with_capacity(vs.len());
13371        for v in vs {
13372            cells.push(match x {
13373                None => v.monad(y, ctx, span)?,
13374                Some(x) => v.dyad(x, y, ctx, span)?,
13375            });
13376        }
13377        return assemble(&[vs.len()], cells, span);
13378    }
13379    if x.is_some() {
13380        return Err(Error::domain("m`:3 has no dyadic meaning", span));
13381    }
13382    let items = if y.rank() == 0 { vec![y.clone()] } else { y.cells(1) };
13383    let Some((last, rest)) = items.split_last() else {
13384        return Err(Error::domain("m`:3 needs an argument with items", span));
13385    };
13386    let mut acc = last.clone();
13387    for (i, item) in rest.iter().enumerate().rev() {
13388        acc = vs[i % vs.len()].dyad(item, &acc, ctx, span)?;
13389    }
13390    Ok(acc)
13391}
13392
13393/// `(f⌺w) y` (Dyalog's stencil): the window of `w` cells centred on each
13394/// cell of y, with the edges filled, and f applied to each. There is one
13395/// size per leading axis of y and the axes past them travel whole, so the
13396/// answer is framed by the axes the windows moved along.
13397fn stencil(u: &Verb, w: &[i64], y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
13398    if w.len() > y.rank() {
13399        return Err(Error::new(
13400            ErrorKind::Rank,
13401            format!("a stencil of {} axis/axes into a rank-{} value", w.len(), y.rank()),
13402            Some(span),
13403        ));
13404    }
13405    if w.iter().any(|&n| n <= 0) {
13406        return Err(Error::domain("a stencil window is a positive size", span));
13407    }
13408    let y = y.to_row_major();
13409    let k = w.len();
13410    let st = strides(&y.shape);
13411    let frame: Vec<usize> = y.shape[..k].to_vec();
13412    // The window's own shape: the sizes, then whatever the cell carries.
13413    let mut wshape: Vec<usize> = w.iter().map(|&n| n as usize).collect();
13414    wshape.extend_from_slice(&y.shape[k..]);
13415    let inner: usize = y.shape[k..].iter().product();
13416    let total: usize = frame.iter().product();
13417    let mut cells = Vec::with_capacity(total);
13418    let mut at = vec![0usize; frame.len()];
13419    let mut coord = vec![0usize; k];
13420    for _ in 0..total {
13421        let mut data = Data::empty(y.dtype());
13422        coord.iter_mut().for_each(|c| *c = 0);
13423        let count: usize = w.iter().map(|&n| n as usize).product();
13424        for _ in 0..count {
13425            let mut base = 0usize;
13426            let mut inside = true;
13427            for a in 0..k {
13428                let off = at[a] as i64 + coord[a] as i64 - (w[a] - 1) / 2;
13429                if off < 0 || off >= y.shape[a] as i64 {
13430                    inside = false;
13431                    break;
13432                }
13433                base += off as usize * st[a];
13434            }
13435            for j in 0..inner {
13436                if inside {
13437                    push_elem(&mut data, &y.data, base + j);
13438                } else {
13439                    data.push_fill();
13440                }
13441            }
13442            odometer(&mut coord, &wshape[..k]);
13443        }
13444        cells.push(u.monad(&Array::new(wshape.clone(), data), ctx, span)?);
13445        odometer(&mut at, &frame);
13446    }
13447    assemble(&frame, cells, span)
13448}
13449
13450/// Whether an insert settles its domain from the whole argument before it
13451/// cuts anything. J answers `2 %/\. 'abc'` with `ca` — a piece of one item
13452/// applies nothing, so the characters are never divided — but refuses
13453/// `2 +/\. 'abc'`, because a sum, a product, a running minimum or maximum
13454/// and an or are the five folds its special code types up front. The set is
13455/// the oracle's, exactly: `*./`, which looks like it belongs, answers.
13456fn folds_eagerly(u: &Verb) -> bool {
13457    let Verb::Reduce(inner) = u else { return false };
13458    matches!(
13459        **inner,
13460        Verb::Prim(Prim {
13461            dyad: DyadOp::Scalar(
13462                ScalarDyad::Add
13463                    | ScalarDyad::Mul
13464                    | ScalarDyad::Min
13465                    | ScalarDyad::Max
13466                    | ScalarDyad::Gcd
13467            ),
13468            ..
13469        })
13470    )
13471}
13472
13473/// `x u\. y`: u applied to y with every run of x consecutive items removed.
13474/// A run of x items has `1 + (#y) - x` places to sit, and that is how many
13475/// results there are.
13476fn outfix(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
13477    let k = one_int(x, "an outfix width", ctx.cfg.near(), span)?;
13478    let n = y.items() as i64;
13479    let list = as_list(y);
13480    // A positive width leaves out every run of x consecutive items, so
13481    // there are `1 + n - x` of them and none at all once x is longer than
13482    // the argument. A negative one leaves out NON-OVERLAPPING runs, the
13483    // last of them short where the length does not divide.
13484    // The widths are the program's own numbers, so the arithmetic that
13485    // turns one into a list of starts runs in i128: `_9223372036854775808`
13486    // has no negation in i64, and `n + step` overflows for a large step.
13487    let starts: Vec<i64> = if k < 0 {
13488        let step = i128::from(k.unsigned_abs());
13489        let count = (i128::from(n) + step - 1) / step;
13490        (0..count).map(|i| (i * step) as i64).collect()
13491    } else {
13492        (0..=(n - k)).collect()
13493    };
13494    let width = k.unsigned_abs() as usize;
13495    // A sum, a product, a running extremum and an or are the folds J has
13496    // special code for, and that code settles its domain from the WHOLE
13497    // argument before any piece is cut: `2 +/\. 'abc'` is a domain error
13498    // although every piece it leaves behind holds one character, and so are
13499    // `_2 +/\. 'ab'` and `4 +/\. 'abc'`, which fold nothing at all. Every
13500    // other fold is asked piece by piece, so `2 %/\. 'abc'` is `ca`. The
13501    // probe is spent on characters and boxes alone, since numeric data
13502    // never fails it -- and on nothing at all when the operand is not pure,
13503    // since a verb that writes must not write twice.
13504    if !list.dtype().is_numeric()
13505        && u.is_pure()
13506        && folds_eagerly(u)
13507        && n >= 1
13508        && (n >= 2 || !starts.is_empty())
13509    {
13510        // The question is whether the operand has a MEANING for this data,
13511        // and a fold of one item answers nothing: `+/ ,'a'` is that one
13512        // character, applying `+` to nothing. So an argument of one item is
13513        // asked with that item twice, which is the smallest fold that
13514        // really applies the operand.
13515        let probe =
13516            if n == 1 { select_items(&list, &[0, 0]) } else { list.clone() };
13517        u.monad(&probe, ctx, span)?;
13518    }
13519    // A width longer than the argument leaves no place for the run to sit.
13520    // The one run an empty argument has is the argument itself, and that is
13521    // the cell whose shape the answer keeps.
13522    if starts.is_empty() {
13523        let cell = u.is_pure().then(|| select_items(&list, &[]));
13524        return Ok(empty_frame(&[0], list.dtype(), cell, ctx, |cell, c| u.monad(cell, c, span)));
13525    }
13526    let mut cells = Vec::with_capacity(starts.len());
13527    for start in starts {
13528        let start = start as usize;
13529        let keep: Vec<usize> =
13530            (0..n as usize).filter(|&i| i < start || i >= start + width).collect();
13531        cells.push(u.monad(&select_items(&list, &keep), ctx, span)?);
13532    }
13533    assemble(&[cells.len()], cells, span)
13534}
13535
13536// ---------------------------------------------------------------- obverses
13537
13538/// The verb that undoes this one, where libjay knows of one.
13539///
13540/// This is J's obverse table, and it is deliberately a table rather than a
13541/// search: a verb is here only when its inverse is another verb libjay can
13542/// already write down. Everything built out of those — the compositions,
13543/// the bonds, `u^:n` — inverts by inverting its parts, so the table stays
13544/// small while `&.`, `&.:` and the negative powers reach a long way past
13545/// it. A verb that is not here has no obverse, and the diagnostic says so
13546/// by name.
13547pub(crate) fn obverse(v: &Verb) -> Option<Verb> {
13548    Some(match v {
13549        Verb::Prim(p) => prim_obverse(v, p)?,
13550        // An explicit obverse (`u :. v`) is the whole answer.
13551        Verb::WithObverse(_, w) => (**w).clone(),
13552        // A composition inverts by inverting its parts, in the other order.
13553        Verb::Atop(f, g) => {
13554            Verb::Atop(Box::new(obverse(g)?), Box::new(obverse(f)?))
13555        }
13556        Verb::Compose(f, g) | Verb::Beside(f, g) => {
13557            Verb::Atop(Box::new(obverse(g)?), Box::new(obverse(f)?))
13558        }
13559        Verb::Rank(f, r) => Verb::Rank(Box::new(obverse(f)?), *r),
13560        Verb::Fit(f, n) => Verb::Fit(Box::new(obverse(f)?), *n),
13561        // `u&.>` and `u¨` undo box by box: the boxing is its own inverse,
13562        // so only the verb inside one has to be turned round.
13563        Verb::Each(f, rule) => Verb::Each(Box::new(obverse(f)?), *rule),
13564        // `*/ y` is the product, and the product of a whole number is
13565        // undone by its prime factors.
13566        Verb::Reduce(f) if is_dyad(f, DyadOp::Scalar(ScalarDyad::Mul)) => named("q:")?,
13567        // The running sums and products, which invert into the differences
13568        // and the quotients between neighbours.
13569        Verb::Windowed(f, kind) => scan_obverse(f, *kind)?,
13570        // `u^:n` undone is `u^:_1` done n times.
13571        Verb::PowerN(f, Power::Times(n)) => {
13572            Verb::PowerN(Box::new(obverse(f)?), Power::Times(*n))
13573        }
13574        Verb::BondLeft(m, f) => bond_obverse(m, f, true)?,
13575        Verb::BondRight(f, n) => bond_obverse(n, f, false)?,
13576        _ => return None,
13577    })
13578}
13579
13580/// A J primitive by its spelling, for the obverses that are one.
13581fn named(spelling: &'static str) -> Option<Verb> {
13582    crate::frontend::j::verb_named(spelling)
13583}
13584
13585/// A verb built here rather than looked up: the inverses J itself spells
13586/// only as `u^:_1`, so they carry that spelling as their name.
13587fn made(name: &'static str, monad: MonadOp, ranks: [i64; 3]) -> Verb {
13588    Verb::Prim(Prim { name, monad, dyad: DyadOp::None, ranks })
13589}
13590
13591fn is_dyad(v: &Verb, op: DyadOp) -> bool {
13592    matches!(v, Verb::Prim(p) if p.dyad == op)
13593}
13594
13595fn atop(f: Verb, g: Verb) -> Verb {
13596    Verb::Atop(Box::new(f), Box::new(g))
13597}
13598
13599/// The obverse of a primitive.
13600fn prim_obverse(v: &Verb, p: &Prim) -> Option<Verb> {
13601    use ScalarMonad as SM;
13602    // Every one of these is its own inverse, whichever language spelled it:
13603    // the verb itself is the answer, so no name is looked up (an APL glyph
13604    // has no entry in J's table). Grade sends a permutation to the
13605    // permutation that undoes it, the cycles of `C.` convert back, and a
13606    // matrix inverse, a set of polynomial roots and the identity verbs all
13607    // return where they came from.
13608    if matches!(
13609        p.monad,
13610        MonadOp::Scalar(SM::Conj | SM::Neg | SM::Recip | SM::OneMinus)
13611            | MonadOp::Reverse
13612            | MonadOp::TransposeAxes
13613            | MonadOp::GradeUp { .. }
13614            | MonadOp::CycleForm
13615            | MonadOp::MatrixInverse
13616            | MonadOp::PolyRoots
13617            | MonadOp::Same
13618    ) {
13619        return Some(v.clone());
13620    }
13621    // `x # y` undone with the same x is the expansion: the items come back
13622    // where the ones stand and a fill takes every place a zero left. It has
13623    // no monadic meaning, since `# y` counts and a count says nothing about
13624    // what was counted.
13625    if p.dyad == DyadOp::Copy {
13626        return Some(expand_verb());
13627    }
13628    let built = match p.monad {
13629        // `j. y` turns y a quarter turn about the origin; turning it back
13630        // is a quarter turn the other way, which is `-@j.`.
13631        MonadOp::Scalar(SM::Imaginary) => atop(named("-")?, named("j.")?),
13632        // `r. y` is `^ 0j1 * y`, so the angle comes back as the logarithm
13633        // turned the same quarter turn back.
13634        MonadOp::Scalar(SM::Polar) => {
13635            atop(atop(named("-")?, named("j.")?), named("^.")?)
13636        }
13637        // `o. y` multiplies by pi, and the reference undoes it by
13638        // multiplying by the reciprocal rather than dividing.
13639        MonadOp::Scalar(SM::Pi) => Verb::BondLeft(
13640            Array::scalar_f64(std::f64::consts::FRAC_1_PI),
13641            Box::new(named("*")?),
13642        ),
13643        // The two readings of a complex number as a pair of reals: the
13644        // pair folds back together under the verb that made it.
13645        MonadOp::ComplexParts { polar } => Verb::Rank(
13646            Box::new(Verb::Reduce(Box::new(named(if polar { "r." } else { "j." })?))),
13647            [1, RANK_INF, RANK_INF],
13648        ),
13649        // Grading down is grading up over the reversed argument.
13650        MonadOp::GradeDown { origin } => atop(
13651            Verb::Prim(Prim {
13652                name: "/:",
13653                monad: MonadOp::GradeUp { origin },
13654                dyad: DyadOp::GradeSelect { down: false },
13655                ranks: [RANK_INF, RANK_INF, RANK_INF],
13656            }),
13657            named("|.")?,
13658        ),
13659        // A list of prime factors multiplies back into its number, one row
13660        // at a time.
13661        MonadOp::PrimeFactors => {
13662            Verb::Rank(Box::new(Verb::Reduce(Box::new(named("*")?))), [1, RANK_INF, RANK_INF])
13663        }
13664        // `;: y` cuts a character list into words; putting a blank after
13665        // each word and razing them joins it back, less the trailing blank.
13666        MonadOp::Words => atop(
13667            named("}:")?,
13668            atop(
13669                named(";")?,
13670                Verb::Each(
13671                    Box::new(Verb::BondRight(
13672                        Box::new(named(",")?),
13673                        Array::from_chars(vec![' ']),
13674                    )),
13675                    Enclose::Always,
13676                ),
13677            ),
13678        ),
13679        // The forms that carry their own inverse in the same spelling.
13680        MonadOp::ToExact => Verb::BondLeft(Array::scalar_i64(-1), Box::new(named("x:")?)),
13681        MonadOp::Unicode { .. } => {
13682            Verb::BondLeft(Array::scalar_i64(3), Box::new(named("u:")?))
13683        }
13684        MonadOp::Symbols => Verb::BondLeft(Array::scalar_i64(5), Box::new(named("s:")?)),
13685        // The three the reference spells only as a negative power.
13686        MonadOp::NthPrime => made("p:^:_1", MonadOp::PrimeCount, [0, 0, 0]),
13687        MonadOp::Sparse => {
13688            made("$.^:_1", MonadOp::Dense, [RANK_INF, RANK_INF, RANK_INF])
13689        }
13690        MonadOp::Indices { origin: 0, boxed_coords: false } => {
13691            made("I.^:_1", MonadOp::IndicesInverse, [1, RANK_INF, RANK_INF])
13692        }
13693        // Formatting and evaluating undo one another in whichever language
13694        // spelled them: `":` with `".`, `⍕` with `⍎`.
13695        MonadOp::Format if p.name == "⍕" => Verb::Prim(Prim {
13696            name: "⍎",
13697            monad: MonadOp::Execute { apl: true },
13698            dyad: DyadOp::None,
13699            ranks: [1, RANK_INF, RANK_INF],
13700        }),
13701        MonadOp::Format => named("\".")?,
13702        MonadOp::Execute { apl: true } => Verb::Prim(Prim {
13703            name: "⍕",
13704            monad: MonadOp::Format,
13705            dyad: DyadOp::FormatSpec,
13706            ranks: [RANK_INF, 1, RANK_INF],
13707        }),
13708        MonadOp::Execute { apl: false } => named("\":")?,
13709        _ => {
13710            let by_monad: Option<&'static str> = match p.monad {
13711                MonadOp::Scalar(SM::Exp) => Some("^."),
13712                MonadOp::Scalar(SM::Ln) => Some("^"),
13713                MonadOp::Scalar(SM::Sqrt) => Some("*:"),
13714                MonadOp::Scalar(SM::Square) => Some("%:"),
13715                MonadOp::Scalar(SM::Double) => Some("-:"),
13716                MonadOp::Scalar(SM::Halve) => Some("+:"),
13717                MonadOp::Scalar(SM::Inc) => Some("<:"),
13718                MonadOp::Scalar(SM::Dec) => Some(">:"),
13719                MonadOp::Enclose(_) => Some(">"),
13720                MonadOp::Open => Some("<"),
13721                MonadOp::DecodeBits => Some("#:"),
13722                MonadOp::EncodeBits => Some("#."),
13723                MonadOp::Itemize => Some("{."),
13724                MonadOp::Head => Some(",:"),
13725                _ => None,
13726            };
13727            named(by_monad?)?
13728        }
13729    };
13730    Some(built)
13731}
13732
13733/// `x #^:_1 y`: the expansion, which is what undoes `x # y`.
13734fn expand_verb() -> Verb {
13735    Verb::Prim(Prim {
13736        name: "#^:_1",
13737        monad: MonadOp::None,
13738        dyad: DyadOp::Expand,
13739        ranks: [RANK_INF, 1, RANK_INF],
13740    })
13741}
13742
13743/// The obverse of a running fold — `+/\`, `-/\.` and their kin.
13744///
13745/// A running sum inverts into the differences between neighbours, a running
13746/// product into the quotients: the argument against itself shifted one
13747/// place, the fill being the operation's identity. The subtracting and
13748/// dividing folds alternate, so their answers carry one further pass over
13749/// the signs `1 _1 1 _1 …`.
13750fn scan_obverse(f: &Verb, kind: WindowKind) -> Option<Verb> {
13751    use ScalarDyad as SD;
13752    let Verb::Reduce(inner) = f else { return None };
13753    let Verb::Prim(p) = &**inner else { return None };
13754    let DyadOp::Scalar(op) = p.dyad else { return None };
13755    let suffix = match kind {
13756        WindowKind::Prefix | WindowKind::Scan => false,
13757        WindowKind::Suffix => true,
13758    };
13759    // The neighbour: one place to the right for a prefix fold, one to the
13760    // left for a suffix one, the vacated place taking the fill.
13761    let fill = match op {
13762        SD::Add | SD::Sub => 0.0,
13763        SD::Mul | SD::DivJ | SD::DivApl => 1.0,
13764        _ => return None,
13765    };
13766    let shift = Verb::ShiftFill(Array::scalar_f64(fill));
13767    let neighbour = if suffix {
13768        Verb::BondLeft(Array::scalar_i64(1), Box::new(shift))
13769    } else {
13770        shift
13771    };
13772    // What takes the argument back to its neighbour: the inverse of the
13773    // fold for a prefix, the fold itself for a suffix.
13774    let step = match (op, suffix) {
13775        (SD::Add, false) | (SD::Sub, false) => named("-")?,
13776        (SD::Add, true) => named("-")?,
13777        (SD::Sub, true) => named("+")?,
13778        (SD::Mul, _) | (SD::DivJ | SD::DivApl, false) => named("%")?,
13779        (SD::DivJ | SD::DivApl, true) => named("*")?,
13780        _ => return None,
13781    };
13782    let differences = Verb::Hook(Box::new(step), Box::new(neighbour));
13783    // A prefix fold under subtraction or division alternates, so every
13784    // second answer is turned round again.
13785    let alternate = matches!((op, suffix), (SD::Sub, false) | (SD::DivJ | SD::DivApl, false));
13786    if !alternate {
13787        return Some(differences);
13788    }
13789    let signs = atop(
13790        Verb::BondRight(Box::new(named("$")?), Array::from_i64(vec![1, -1])),
13791        named("#")?,
13792    );
13793    let apply = if matches!(op, SD::Sub) { named("*")? } else { named("^")? };
13794    Some(Verb::Fork(Box::new(differences), Box::new(apply), Box::new(signs)))
13795}
13796
13797/// The obverse of a bonded arithmetic verb. `left` says which side the noun
13798/// was bonded to, which is what tells `n - y` (its own inverse) from
13799/// `y - n` (whose inverse adds).
13800fn bond_obverse(n: &Array, f: &Verb, left: bool) -> Option<Verb> {
13801    // `u~&n` is `n&u` written the other way round, so it inverts the same
13802    // way. The reference gives `n&u~` no obverse, and neither does this.
13803    if let Verb::Commute(g) = f {
13804        if !left {
13805            return bond_obverse(n, g, true);
13806        }
13807        return None;
13808    }
13809    if let Some(v) = structural_bond_obverse(n, f, left) {
13810        return Some(v);
13811    }
13812    let Verb::Prim(p) = f else { return None };
13813    let bond = |name: &'static str, arg: &Array| -> Option<Verb> {
13814        let g = named(name)?;
13815        Some(if left {
13816            Verb::BondLeft(arg.clone(), Box::new(g))
13817        } else {
13818            Verb::BondRight(Box::new(g), arg.clone())
13819        })
13820    };
13821    use ScalarDyad as SD;
13822    let DyadOp::Scalar(op) = p.dyad else { return None };
13823    if matches!(op, SD::Circle) {
13824        // `n o. y` is undone by `(-n) o. y`: the circle functions are
13825        // numbered so that the negative index is the inverse.
13826        return left.then(|| Some(Verb::BondLeft(negated(n)?, Box::new(named("o.")?))))?;
13827    }
13828    match (op, left) {
13829        // `n - y` and `n % y` undo themselves; the other side does not.
13830        (SD::Sub | SD::DivJ | SD::DivApl, true) => bond(p.name, n),
13831        // Adding or multiplying is undone by taking the noun off the
13832        // RIGHT, whichever side it was bonded to: `2&+` is undone by `-&2`
13833        // and not by `2&-`.
13834        (SD::Add, _) => Some(Verb::BondRight(Box::new(named("-")?), n.clone())),
13835        (SD::Mul, _) => Some(Verb::BondRight(Box::new(named("%")?), n.clone())),
13836        (SD::Sub, false) => bond("+", n),
13837        (SD::DivJ | SD::DivApl, false) => bond("*", n),
13838        // `y ^ n` is undone by the n-th root; `n ^ y` by the base-n log.
13839        (SD::Pow, false) => Some(Verb::BondLeft(n.clone(), Box::new(named("%:")?))),
13840        (SD::Pow, true) => Some(Verb::BondLeft(n.clone(), Box::new(named("^.")?))),
13841        // `n ^. y` is the logarithm to the base n, which raising n to the
13842        // answer turns back; `n %: y` is the n-th root, which the n-th
13843        // POWER turns back, and the noun changes sides for it.
13844        (SD::Log, true) => Some(Verb::BondLeft(n.clone(), Box::new(named("^")?))),
13845        (SD::Root, true) => Some(Verb::BondRight(Box::new(named("^")?), n.clone())),
13846        // `y ^. n` is the logarithm of n to the base y, which the y-th root
13847        // of n turns back, and the other way round.
13848        (SD::Log, false) => Some(Verb::BondRight(Box::new(named("%:")?), n.clone())),
13849        (SD::Root, false) => Some(Verb::BondRight(Box::new(named("^.")?), n.clone())),
13850        _ => None,
13851    }
13852}
13853
13854/// The noun with every value negated, for the bonds whose obverse is the
13855/// same verb with the opposite parameter.
13856fn negated(n: &Array) -> Option<Array> {
13857    if let Some(v) = n.to_i64_vec() {
13858        let out: Vec<i64> = v.iter().map(|&k| -k).collect();
13859        return Some(Array::new(n.shape.clone(), Data::I64(out.into())));
13860    }
13861    let v = n.to_f64_vec()?;
13862    let out: Vec<f64> = v.iter().map(|&k| -k).collect();
13863    Some(Array::new(n.shape.clone(), Data::F64(out.into())))
13864}
13865
13866/// The one number a bond's noun holds, for the bonds whose obverse needs
13867/// its value rather than only its shape.
13868fn one_number(n: &Array) -> Option<f64> {
13869    match n.to_f64_vec()?[..] {
13870        [v] if n.rank() <= 1 => Some(v),
13871        _ => None,
13872    }
13873}
13874
13875/// The obverse of a bond whose verb rearranges rather than computes: the
13876/// rotations, the drops and appends, the base conversions and the two
13877/// permutation forms.
13878fn structural_bond_obverse(n: &Array, f: &Verb, left: bool) -> Option<Verb> {
13879    // APL wraps some of its primitives in the rank that picks the axis, so
13880    // the primitive is looked for under one; the rules that keep the verb
13881    // and change only the noun keep that wrapper with it.
13882    let p = match f {
13883        Verb::Prim(p) => p,
13884        Verb::Rank(inner, _) => match &**inner {
13885            Verb::Prim(p) => p,
13886            _ => return None,
13887        },
13888        _ => return None,
13889    };
13890    match (p.dyad, left) {
13891        // `n |. y` is undone by rotating the other way.
13892        (DyadOp::Rotate | DyadOp::RotateApl { .. }, true) => {
13893            Some(Verb::BondLeft(negated(n)?, Box::new(f.clone())))
13894        }
13895        // `x # y` bonded keeps its expansion.
13896        (DyadOp::Copy, true) => Some(Verb::BondLeft(n.clone(), Box::new(expand_verb()))),
13897        // Appending a fixed noun is undone by dropping as many items as it
13898        // brought — off the end when it was appended there, off the front
13899        // when it went in front.
13900        (DyadOp::AppendLeading | DyadOp::AppendLast, _) => {
13901            let items = if n.rank() == 0 { 1 } else { n.shape[0] } as i64;
13902            let count = if left { items } else { -items };
13903            Some(Verb::BondLeft(Array::scalar_i64(count), Box::new(named("}.")?)))
13904        }
13905        // `n }. y` is undone by taking back what was dropped: as many items
13906        // as the argument has now plus the ones that went, from the end the
13907        // drop did not touch, so the vacated places take a fill.
13908        (DyadOp::Drop, true) => {
13909            let k = one_number(n)?;
13910            let size = Verb::Atop(
13911                Box::new(Verb::BondLeft(Array::scalar_f64(k.abs()), Box::new(named("+")?))),
13912                Box::new(named("#")?),
13913            );
13914            let width = if k >= 0.0 { atop(named("-")?, size) } else { size };
13915            Some(Verb::Hook(
13916                Box::new(Verb::Commute(Box::new(named("{.")?))),
13917                Box::new(width),
13918            ))
13919        }
13920        // `n #. y` reads a list of digits in base n; undoing it writes the
13921        // digits back, in as many places as the largest value asks for.
13922        (DyadOp::Decode, true) => {
13923            let width = atop(
13924                Verb::BondRight(Box::new(named("$")?), n.clone()),
13925                atop(
13926                    named(">:")?,
13927                    atop(
13928                        named("<.")?,
13929                        atop(
13930                            Verb::BondLeft(n.clone(), Box::new(named("^.")?)),
13931                            atop(
13932                                Verb::BondLeft(Array::scalar_i64(1), Box::new(named(">.")?)),
13933                                atop(
13934                                    Verb::Reduce(Box::new(named(">.")?)),
13935                                    atop(named("|")?, named(",")?),
13936                                ),
13937                            ),
13938                        ),
13939                    ),
13940                ),
13941            );
13942            Some(Verb::Fork(Box::new(width), Box::new(named("#:")?), Box::new(named("]")?)))
13943        }
13944        // `n #: y` writes the digits, and reading them back is `n #. y`.
13945        (DyadOp::Encode, true) => Some(Verb::BondLeft(n.clone(), Box::new(named("#.")?))),
13946        // `n A. y` and `n C. y` permute; the permutation that undoes them is
13947        // the one they make of `i. # y`, graded.
13948        (DyadOp::AnagramFrom | DyadOp::Permute, true) => {
13949            let spelling = if p.dyad == DyadOp::AnagramFrom { "A." } else { "C." };
13950            let inverse = atop(
13951                atop(named("/:")?, Verb::BondLeft(n.clone(), Box::new(named(spelling)?))),
13952                atop(named("i.")?, named("#")?),
13953            );
13954            Some(Verb::Fork(Box::new(inverse), Box::new(named("{")?), Box::new(named("]")?)))
13955        }
13956        _ => None,
13957    }
13958}
13959
13960// ------------------------------------------------- classification and sets
13961
13962/// `= y`: one row per distinct item, marking where that item stands. A
13963/// scalar has one item, so it answers a 1×1 table.
13964fn self_classify(y: &Array, tol: Tol) -> Array {
13965    let items = if y.rank() == 0 { 1 } else { y.items() };
13966    let keys = nub(&as_list(y), tol);
13967    let rows = keys.items();
13968    let mut out = Vec::with_capacity(rows * items);
13969    for i in 0..rows {
13970        let key = item_or_self(&keys, i);
13971        for j in 0..items {
13972            out.push(arrays_match(&key, &item_or_self(y, j), tol) as u8);
13973        }
13974    }
13975    Array::new(vec![rows, items], Data::Bool(out.into()))
13976}
13977
13978/// `~: y` / `≠ y`: 1 where a value has not been seen before.
13979///
13980/// The two languages count different things. J's sieve runs over ITEMS and
13981/// answers one bit per item, so a matrix gives a vector. APL's runs over
13982/// the ELEMENTS in ravel order and keeps the argument's own shape, so a
13983/// matrix gives a matrix and a scalar gives a scalar.
13984fn nub_sieve(y: &Array, tol: Tol, lang: crate::Lang) -> Array {
13985    let by_element = lang == crate::Lang::Apl;
13986    let n = if by_element {
13987        y.count()
13988    } else if y.rank() == 0 {
13989        1
13990    } else {
13991        y.items()
13992    };
13993    let mut seen: Vec<Array> = Vec::new();
13994    let mut out = Vec::with_capacity(n);
13995    for i in 0..n {
13996        let cell = if by_element {
13997            Array::new(Vec::new(), y.data.slice(i, i + 1))
13998        } else {
13999            item_or_self(y, i)
14000        };
14001        let fresh = !seen.iter().any(|s| arrays_match(s, &cell, tol));
14002        if fresh {
14003            seen.push(cell);
14004        }
14005        out.push(fresh as u8);
14006    }
14007    let shape = if by_element { y.shape.clone() } else { vec![n] };
14008    Array::new(shape, Data::Bool(out.into()))
14009}
14010
14011/// A rank-0 argument as the one-item list it behaves as for the set verbs.
14012fn as_list(y: &Array) -> Array {
14013    if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() }
14014}
14015
14016/// The values of `y` that an item of shape `item_rank` could match: y's
14017/// cells of that rank, framed by whatever axes are left. A y with no room
14018/// for a frame is one such value, which is what lets `(i.3 2) -. 2 3`
14019/// remove the row rather than nothing.
14020fn conforming_cells(y: &Array, item_rank: usize) -> Vec<Array> {
14021    let frame_rank = y.rank().saturating_sub(item_rank);
14022    let nf: usize = y.shape[..frame_rank].iter().product();
14023    (0..nf).map(|i| y.cell_at(frame_rank, i)).collect()
14024}
14025
14026/// Which items of `y` occur among the values of `x` that could match one.
14027fn item_marks(y: &Array, x: &Array, tol: Tol) -> Vec<bool> {
14028    let n = if y.rank() == 0 { 1 } else { y.items() };
14029    let item_rank = y.rank().saturating_sub(1);
14030    let against = conforming_cells(x, item_rank);
14031    (0..n)
14032        .map(|i| {
14033            let cell = item_or_self(y, i);
14034            against.iter().any(|c| arrays_match(&cell, c, tol))
14035        })
14036        .collect()
14037}
14038
14039/// `x -. y` / `x ~ y`: x's items with the ones y also has removed.
14040fn set_less(x: &Array, y: &Array, tol: Tol) -> Array {
14041    let xs = as_list(x);
14042    let marks = item_marks(&xs, y, tol);
14043    let keep: Vec<usize> = (0..marks.len()).filter(|&i| !marks[i]).collect();
14044    select_items(&xs, &keep)
14045}
14046
14047/// APL's set functions read their arguments as lists and refuse anything
14048/// deeper: `1 2∩2 3⍴⍳6` is a RANK ERROR where J's `-.` and `~.` would work
14049/// on the items of a table.
14050fn set_rank(cfg: EvalCfg, what: &str, x: &Array, y: &Array, span: Span) -> Result<()> {
14051    if cfg.rules.lang == crate::Lang::Apl && (x.rank() > 1 || y.rank() > 1) {
14052        return Err(Error::new(
14053            ErrorKind::Rank,
14054            format!("{what} takes vectors, not rank {} and rank {}", x.rank(), y.rank()),
14055            Some(span),
14056        ));
14057    }
14058    Ok(())
14059}
14060
14061/// `x ∩ y`: x's items that y also has, in x's order and with x's repeats.
14062fn intersect_items(x: &Array, y: &Array, tol: Tol) -> Array {
14063    let xs = as_list(x);
14064    let marks = item_marks(&xs, y, tol);
14065    let keep: Vec<usize> = (0..marks.len()).filter(|&i| marks[i]).collect();
14066    select_items(&xs, &keep)
14067}
14068
14069/// `x ∪ y`: x's items, then the items of y that are new. x keeps whatever
14070/// repeats it has; APL's union only sieves the right argument.
14071fn union_items(x: &Array, y: &Array, tol: Tol, span: Span) -> Result<Array> {
14072    let xs = as_list(x);
14073    let ys = as_list(y);
14074    let marks = item_marks(&ys, &xs, tol);
14075    let mut extra: Vec<usize> = Vec::new();
14076    for (i, &seen) in marks.iter().enumerate() {
14077        if seen {
14078            continue;
14079        }
14080        let cell = item_or_self(&ys, i);
14081        if !extra.iter().any(|&j| arrays_match(&item_or_self(&ys, j), &cell, tol)) {
14082            extra.push(i);
14083        }
14084    }
14085    catenate(&xs, &select_items(&ys, &extra), true, false, span)
14086}
14087
14088/// `x E. y` / `x ⍷ y`: 1 at each position of y where a copy of x begins.
14089/// The answer is shaped like y, and the search runs over all of y's axes at
14090/// once, so a table is looked for inside a table. A pattern that would run
14091/// off an edge matches nowhere; an EMPTY pattern matches everywhere, being
14092/// a run of no elements.
14093///
14094/// The two languages align the pattern differently: J wants the two ranks
14095/// to agree, counting a scalar pattern as a one-element list, while APL
14096/// pads the pattern with leading axes of one and takes any rank up to y's.
14097fn find_seq(x: &Array, y: &Array, tol: Tol, apl: bool, span: Span) -> Result<Array> {
14098    let (xr, yr) = (x.rank(), y.rank());
14099    // J reads an atom as a one-item list on BOTH sides, so a pattern of
14100    // one atom has exactly one place to sit in an argument of one atom:
14101    // `0 E. 5` is 0 and `1 E. 1` is 1, both of them scalars.
14102    if !apl && xr == 0 && yr == 0 {
14103        let hit = arrays_match(x, y, tol);
14104        return Ok(Array::new(Vec::new(), Data::Bool(vec![u8::from(hit)].into())));
14105    }
14106    if apl && xr > yr {
14107        // A pattern with more axes than the argument fits nowhere in it.
14108        return Ok(Array::new(y.shape.clone(), Data::Bool(vec![0u8; y.count()].into())));
14109    }
14110    if !apl && xr.max(1) != yr {
14111        return Err(Error::new(
14112            ErrorKind::Rank,
14113            format!("a rank-{xr} pattern in a rank-{yr} argument"),
14114            Some(span),
14115        ));
14116    }
14117    let mut pattern = vec![1usize; yr];
14118    pattern[yr - xr..].copy_from_slice(&x.shape);
14119    let n = y.count();
14120    let mut out = vec![0u8; n];
14121    let (xrm, yrm) = (x.to_row_major(), y.to_row_major());
14122    let yst = strides(&y.shape);
14123    let cells: usize = pattern.iter().product();
14124    let mut at = vec![0usize; yr];
14125    for slot in out.iter_mut() {
14126        if (0..yr).all(|a| at[a] + pattern[a] <= y.shape[a]) {
14127            let mut off = vec![0usize; yr];
14128            let mut hit = true;
14129            for k in 0..cells {
14130                let i: usize = (0..yr).map(|a| (at[a] + off[a]) * yst[a]).sum();
14131                if !arrays_match(&atom(&xrm, k), &atom(&yrm, i), tol) {
14132                    hit = false;
14133                    break;
14134                }
14135                odometer(&mut off, &pattern);
14136            }
14137            *slot = hit as u8;
14138        }
14139        odometer(&mut at, &y.shape);
14140    }
14141    Ok(Array::new(y.shape.clone(), Data::Bool(out.into())))
14142}
14143
14144/// `+:` and `*:` dyadically, and APL's `⍱` and `⍲`: both arguments must
14145/// already be booleans, which is the only domain either reference gives
14146/// them.
14147fn bool_dyad(op: BoolDyad, x: &Array, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
14148    let bit = |a: &Array| -> Result<u8> {
14149        match a.to_i64_vec().as_deref() {
14150            Some([0]) => Ok(0),
14151            Some([1]) => Ok(1),
14152            _ => Err(Error::domain("this verb reads values of 0 or 1", span)),
14153        }
14154    };
14155    let _ = cfg;
14156    let (a, b) = (bit(x)?, bit(y)?);
14157    let v = match op {
14158        BoolDyad::Nor => u8::from(a == 0 && b == 0),
14159        BoolDyad::Nand => u8::from(a == 0 || b == 0),
14160    };
14161    Ok(Array::new(vec![], Data::Bool(vec![v].into())))
14162}
14163
14164// ------------------------------------------------------------ permutations
14165
14166/// The ranks of y's items: the position each would take in a stable sort.
14167/// This is the permutation `A.` reports the index of, which is why a list
14168/// that is not itself a permutation still has an anagram index.
14169fn item_ranks(y: &Array, rules: Rules, span: Span) -> Result<Vec<usize>> {
14170    check_gradable(y, rules, span)?;
14171    if !y.dtype().is_numeric() {
14172        return Err(Error::domain("an anagram index needs numbers", span));
14173    }
14174    let order = grade_order(&as_list(y), false, Grading::of(rules, rules.tol()));
14175    let mut ranks = vec![0usize; order.len()];
14176    for (place, &i) in order.iter().enumerate() {
14177        ranks[i] = place;
14178    }
14179    Ok(ranks)
14180}
14181
14182/// `A. y`: where the permutation y's items rank as stands in the
14183/// lexicographic list of the permutations of that length.
14184fn anagram_index(y: &Array, rules: Rules, span: Span) -> Result<Array> {
14185    let ranks = item_ranks(y, rules, span)?;
14186    let n = ranks.len();
14187    let mut index: i128 = 0;
14188    for i in 0..n {
14189        let smaller = ranks[i + 1..].iter().filter(|&&r| r < ranks[i]).count() as i128;
14190        index = index
14191            .checked_mul((n - i) as i128)
14192            .and_then(|v| v.checked_add(smaller))
14193            .ok_or_else(|| Error::not_yet("an anagram index too large for an integer", span))?;
14194    }
14195    i64::try_from(index)
14196        .map(Array::scalar_i64)
14197        .map_err(|_| Error::not_yet("an anagram index too large for an integer", span))
14198}
14199
14200/// `x A. y`: y's items in the order the x-th permutation puts them. A
14201/// negative x counts back from the last permutation, as J's does.
14202fn anagram_from(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14203    let ys = as_list(y);
14204    let n = ys.items();
14205    let mut total: i128 = 1;
14206    for k in 1..=n as i128 {
14207        total = total
14208            .checked_mul(k)
14209            .ok_or_else(|| Error::not_yet("permuting more items than an integer counts", span))?;
14210    }
14211    // The index is an integer wherever it picks an item. With no item to
14212    // permute there is exactly one arrangement and no digit to read from
14213    // the index, so J holds a number to the range alone: `0.5 A. i.0` is
14214    // the empty and `1.5 A. i.0` is out of range. A character or a box is
14215    // no index either way.
14216    let out_of_range = |want: &dyn std::fmt::Display| {
14217        Error::domain(
14218            format!("permutation {want} is out of range: {n} items have {total} of them"),
14219            span,
14220        )
14221    };
14222    let mut at: i128 = match x.to_i64_vec_near(near) {
14223        Some(v) => {
14224            let want = i128::from(
14225                *v.first().ok_or_else(|| Error::internal("anagram with no index"))?,
14226            );
14227            let at = if want < 0 { want + total } else { want };
14228            if at < 0 || at >= total {
14229                return Err(out_of_range(&want));
14230            }
14231            at
14232        }
14233        None => {
14234            if n != 0 {
14235                return Err(Error::domain("an anagram index must be an integer", span));
14236            }
14237            let want = *x
14238                .to_f64_vec()
14239                .as_deref()
14240                .and_then(<[f64]>::first)
14241                .ok_or_else(|| Error::domain("an anagram index must be an integer", span))?;
14242            let at = if want < 0.0 { want + total as f64 } else { want };
14243            if !(0.0..total as f64).contains(&at) {
14244                return Err(out_of_range(&want));
14245            }
14246            at as i128
14247        }
14248    };
14249    // The factorial number system, read most significant digit first: each
14250    // digit picks one of the items still unused.
14251    let mut pool: Vec<usize> = (0..n).collect();
14252    let mut order = Vec::with_capacity(n);
14253    let mut fact = total;
14254    for i in 0..n {
14255        fact /= (n - i) as i128;
14256        let d = (at / fact) as usize;
14257        at %= fact;
14258        order.push(pool.remove(d));
14259    }
14260    Ok(select_items(&ys, &order))
14261}
14262
14263/// `C. y`: the two directions between a direct permutation and its cycles.
14264/// A boxed argument holds cycles and answers the permutation; anything else
14265/// is a permutation and answers its cycles. A list shorter than the
14266/// permutation it names stands for one over `1 + >./ y` items, so
14267/// `C. 3 4 2` is the cycles of `0 1 3 4 2`.
14268fn cycle_form(y: &Array, near: NearInt, span: Span) -> Result<Array> {
14269    if y.dtype() == DType::Box {
14270        let perm = cycles_to_direct(y, None, near, span)?;
14271        return Ok(Array::from_i64(perm.iter().map(|&i| i as i64).collect()));
14272    }
14273    let n = permutation_span(y, near, span)?;
14274    let perm = direct_permutation_of(y, n, near, span)?;
14275    let mut boxes: Vec<Array> = Vec::new();
14276    let mut done = vec![false; perm.len()];
14277    for start in 0..perm.len() {
14278        if done[start] {
14279            continue;
14280        }
14281        let mut cycle = Vec::new();
14282        let mut at = start;
14283        while !done[at] {
14284            done[at] = true;
14285            cycle.push(at);
14286            at = perm[at];
14287        }
14288        // J writes each cycle starting at its largest element, and lists
14289        // the cycles in order of those.
14290        let top = cycle.iter().position(|&v| v == *cycle.iter().max().unwrap()).unwrap();
14291        cycle.rotate_left(top);
14292        boxes.push(Array::boxed(Array::from_i64(
14293            cycle.iter().map(|&i| i as i64).collect(),
14294        )));
14295    }
14296    boxes.sort_by_key(|b| b.as_boxes().map(|s| s[0].to_i64_vec().unwrap()[0]).unwrap_or(0));
14297    let n = boxes.len();
14298    let inner: Vec<Array> =
14299        boxes.into_iter().map(|b| b.as_boxes().unwrap()[0].clone()).collect();
14300    Ok(Array::new(vec![n], Data::Box(inner.into())))
14301}
14302
14303/// A direct permutation of `n` items, from a list that may be shorter than
14304/// one. A short list is J's ABBREVIATED permutation: the items it never
14305/// mentions come first, in ascending order, and the list itself is the
14306/// tail. `3 4 2` over five items is `0 1 3 4 2`; `2` over five is the same
14307/// permutation again, and `2 3` over four is the identity.
14308///
14309/// `n` is the count the context supplies — the length of the argument being
14310/// permuted, or for `C. y` one past the largest index the list names.
14311fn direct_permutation_of(y: &Array, n: usize, near: NearInt, span: Span) -> Result<Vec<usize>> {
14312    let v = y
14313        .to_i64_vec_near(near)
14314        .ok_or_else(|| Error::domain("a permutation is a list of integers", span))?;
14315    let mut seen = vec![false; n];
14316    let mut tail = Vec::with_capacity(v.len());
14317    for &i in &v {
14318        let k = usize::try_from(i).ok().filter(|&k| k < n && !seen[k]).ok_or_else(|| {
14319            Error::domain(format!("{i} does not belong to a permutation of {n} items"), span)
14320        })?;
14321        seen[k] = true;
14322        tail.push(k);
14323    }
14324    let mut out: Vec<usize> = (0..n).filter(|&k| !seen[k]).collect();
14325    out.append(&mut tail);
14326    Ok(out)
14327}
14328
14329/// How many items a permutation list stands for on its own: one past the
14330/// largest index it names, and never fewer than the indices it has.
14331fn permutation_span(y: &Array, near: NearInt, span: Span) -> Result<usize> {
14332    let v = y
14333        .to_i64_vec_near(near)
14334        .ok_or_else(|| Error::domain("a permutation is a list of integers", span))?;
14335    let top = v.iter().copied().max().unwrap_or(-1).saturating_add(1).max(0) as u128;
14336    Ok(crate::limits::count(top, span)?.max(v.len()))
14337}
14338
14339/// The direct permutation a boxed list of cycles stands for. Its length is
14340/// one past the largest element any cycle mentions; everything unmentioned
14341/// stays where it is.
14342///
14343/// `within` is how many items the cycles are about to permute, when the
14344/// caller has them: an element then counts back from the end where it is
14345/// negative and names an item that exists, and the permutation is never
14346/// longer than that. Without it — `C. y` alone, which answers a permutation
14347/// of whatever length the cycles ask for — an element is a plain index, and
14348/// the length it asks for is held to the element ceiling rather than
14349/// allocated on trust.
14350fn cycles_to_direct(
14351    y: &Array,
14352    within: Option<usize>,
14353    near: NearInt,
14354    span: Span,
14355) -> Result<Vec<usize>> {
14356    let boxes = y.as_boxes().ok_or_else(|| Error::internal("cycles from a simple array"))?;
14357    let mut cycles: Vec<Vec<usize>> = Vec::new();
14358    let mut top = 0usize;
14359    for b in boxes {
14360        let v = b
14361            .to_i64_vec_near(near)
14362            .ok_or_else(|| Error::domain("a cycle is a list of integers", span))?;
14363        let mut cycle = Vec::with_capacity(v.len());
14364        for &i in &v {
14365            let k = match within {
14366                Some(n) => {
14367                    let at = if i < 0 { i.checked_add(n as i64) } else { Some(i) };
14368                    usize::try_from(at.unwrap_or(-1))
14369                        .ok()
14370                        .filter(|&k| k < n)
14371                        .ok_or_else(|| {
14372                            Error::domain(
14373                                format!("{i} is not an index into {n} item(s)"),
14374                                span,
14375                            )
14376                        })?
14377                }
14378                None => {
14379                    let k = usize::try_from(i)
14380                        .map_err(|_| Error::domain(format!("{i} is not an index"), span))?;
14381                    crate::limits::count(k as u128 + 1, span)?;
14382                    k
14383                }
14384            };
14385            top = top.max(k + 1);
14386            cycle.push(k);
14387        }
14388        cycles.push(cycle);
14389    }
14390    let mut perm: Vec<usize> = (0..top).collect();
14391    for cycle in &cycles {
14392        for w in 0..cycle.len() {
14393            // Cycle (a b c) sends a's slot to b's item, b's to c's, c's to a's.
14394            perm[cycle[w]] = cycle[(w + 1) % cycle.len()];
14395        }
14396    }
14397    Ok(perm)
14398}
14399
14400/// `x C. y`: y's items permuted by x. A boxed x holds cycles; a numeric x
14401/// is a direct permutation of y's items, abbreviated where it is shorter
14402/// than y — the items it never names come first, in ascending order. An
14403/// atom is such a list of one, so `2 C. i.5` and `3 4 2 C. i.5` are the
14404/// same permutation.
14405fn permute(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14406    let ys = as_list(y);
14407    let n = ys.items();
14408    if x.dtype() != DType::Box {
14409        let perm = direct_permutation_of(&as_list(x), n, near, span)?;
14410        return Ok(select_items(&ys, &perm));
14411    }
14412    let mut perm = cycles_to_direct(x, Some(n), near, span)?;
14413    // Cycles name only what moves: everything else stays put.
14414    perm.extend(perm.len()..n);
14415    Ok(select_items(&ys, &perm))
14416}
14417
14418// ------------------------------------------------------- text and structure
14419
14420/// `u: y` and `⎕UCS`: characters and their codepoints. `pass_chars` is J's
14421/// monad, which answers characters with themselves; APL's `⎕UCS` converts
14422/// in both directions.
14423fn unicode(y: &Array, pass_chars: bool, near: NearInt, span: Span) -> Result<Array> {
14424    if y.dtype() == DType::Char {
14425        if pass_chars {
14426            return Ok(y.clone());
14427        }
14428        return Ok(chars_to_codes(y));
14429    }
14430    codes_to_chars(y, near, span)
14431}
14432
14433fn chars_to_codes(y: &Array) -> Array {
14434    let Data::Char(v) = &y.data else { return y.clone() };
14435    Array::new(y.shape.clone(), Data::I64(v.iter().map(|&c| c as i64).collect()))
14436}
14437
14438fn codes_to_chars(y: &Array, near: NearInt, span: Span) -> Result<Array> {
14439    let v = y
14440        .to_i64_vec_near(near)
14441        .ok_or_else(|| Error::domain("a codepoint must be an integer", span))?;
14442    let mut out = Vec::with_capacity(v.len());
14443    for &c in &v {
14444        let ch = u32::try_from(c).ok().and_then(char::from_u32).ok_or_else(|| {
14445            Error::domain(format!("{c} is not a Unicode codepoint"), span)
14446        })?;
14447        out.push(ch);
14448    }
14449    Ok(Array::new(y.shape.clone(), Data::Char(out.into())))
14450}
14451
14452/// `x u: y`: 3 asks for codepoints, 10 for the characters they name. The
14453/// other forms J defines are byte-oriented and are named, not guessed at.
14454fn unicode_form(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14455    let form = x
14456        .to_i64_vec()
14457        .ok_or_else(|| Error::domain("a conversion form is an integer", span))?
14458        .first()
14459        .copied()
14460        .unwrap_or(0);
14461    match form {
14462        3 if y.dtype() == DType::Char => Ok(chars_to_codes(y)),
14463        3 => Err(Error::domain("form 3 converts characters to codepoints", span)),
14464        10 => codes_to_chars(y, near, span),
14465        n => Err(Error::not_yet(format!("the byte-oriented unicode form ({n} u:)"), span)),
14466    }
14467}
14468
14469/// `s: y`: the argument's text, interned.
14470///
14471/// A character list carries its own delimiter in its first position, so
14472/// the two names of a list that begins with a backtick are what stands
14473/// between the backticks, and `s: 'a b'` is the one name `" b"`; the empty
14474/// list has no delimiter and no names. A character table gives one name per
14475/// row, trailing blanks trimmed, and its leading axes are the result's
14476/// shape. A boxed argument gives one name per box, the characters taken
14477/// exactly as they stand — a box is where a name with a trailing blank
14478/// comes from.
14479fn to_symbols(y: &Array, span: Span) -> Result<Array> {
14480    if let Some(boxes) = y.as_boxes() {
14481        let mut ids = Vec::with_capacity(boxes.len());
14482        for b in boxes {
14483            if b.rank() > 1 {
14484                return Err(Error::new(
14485                    ErrorKind::Rank,
14486                    "a boxed symbol name is a character list",
14487                    Some(span),
14488                ));
14489            }
14490            let row_major = b.to_row_major();
14491            let Data::Char(v) = &row_major.data else {
14492                if b.count() == 0 {
14493                    ids.push(crate::symbol::EMPTY);
14494                    continue;
14495                }
14496                return Err(Error::domain("a symbol is made from characters", span));
14497            };
14498            ids.push(crate::symbol::intern(&v.as_slice().iter().collect::<String>()));
14499        }
14500        return Ok(Array::new(y.shape.clone(), Data::Symbol(ids.into())));
14501    }
14502    let row_major = y.to_row_major();
14503    let Data::Char(v) = &row_major.data else {
14504        return Err(Error::domain(
14505            format!("s: makes symbols from characters, not {} data", y.dtype().name()),
14506            span,
14507        ));
14508    };
14509    let chars = v.as_slice();
14510    if y.rank() >= 2 {
14511        let width = y.shape[y.rank() - 1];
14512        let mut ids = Vec::with_capacity(chars.len() / width.max(1));
14513        for row in chars.chunks(width) {
14514            let name: String = row.iter().collect();
14515            ids.push(crate::symbol::intern(name.trim_end_matches(' ')));
14516        }
14517        return Ok(Array::new(y.shape[..y.rank() - 1].to_vec(), Data::Symbol(ids.into())));
14518    }
14519    let Some((&delim, rest)) = chars.split_first() else {
14520        return Ok(Array::new(vec![0], Data::empty(DType::Symbol)));
14521    };
14522    let mut ids = Vec::new();
14523    let mut name = String::new();
14524    for &c in rest {
14525        if c == delim {
14526            ids.push(crate::symbol::intern(&name));
14527            name.clear();
14528        } else {
14529            name.push(c);
14530        }
14531    }
14532    ids.push(crate::symbol::intern(&name));
14533    Ok(Array::new(vec![ids.len()], Data::Symbol(ids.into())))
14534}
14535
14536/// `x s: y`: the numbered symbol forms. 4 lays the names out as a character
14537/// table, blank-padded to the longest, and 5 boxes them one apiece. The
14538/// remaining numbers J defines report on its own symbol table — how many
14539/// slots it holds, which are in use, how it hashes them — and describe an
14540/// interpreter's internals rather than the language.
14541fn symbol_form(x: &Array, y: &Array, span: Span) -> Result<Array> {
14542    let form = x
14543        .to_i64_vec()
14544        .ok_or_else(|| Error::domain("a symbol form is an integer", span))?
14545        .first()
14546        .copied()
14547        .unwrap_or(0);
14548    if !matches!(form, 4 | 5) {
14549        return Err(Error::not_yet(format!("the symbol-table form ({form} s:)"), span));
14550    }
14551    let row_major = y.to_row_major();
14552    let Data::Symbol(ids) = &row_major.data else {
14553        return Err(Error::domain(
14554            format!("{form} s: reads symbols, not {} data", y.dtype().name()),
14555            span,
14556        ));
14557    };
14558    let names = crate::symbol::names(ids.as_slice());
14559    if form == 5 {
14560        let boxes: Vec<Array> =
14561            names.iter().map(|n| Array::from_chars(n.chars().collect())).collect();
14562        return Ok(Array::new(y.shape.clone(), Data::Box(boxes.into())));
14563    }
14564    let width = names.iter().map(|n| n.chars().count()).max().unwrap_or(0);
14565    let mut out: Vec<char> = Vec::with_capacity(names.len() * width);
14566    for n in &names {
14567        out.extend(n.chars());
14568        out.resize(out.len() + width - n.chars().count(), ' ');
14569    }
14570    let mut shape = y.shape.clone();
14571    shape.push(width);
14572    Ok(Array::new(shape, Data::Char(out.into())))
14573}
14574
14575/// `x $. y`: the numbered sparse forms.
14576///
14577/// `0` moves between the two storage kinds in whichever direction the
14578/// argument is not already in, and `1` builds a new sparse array from a
14579/// shape. The rest ask about a sparse argument: `_1` its shape, sparse axes
14580/// and sparse element boxed, `2` the sparse axes, `3` the sparse element,
14581/// `4` the stored index rows, `5` the stored cells, `7` how many entries
14582/// are stored, and `8` the same array with the entries that hold the sparse
14583/// element dropped. `2` also answers a dense argument, which has all of its
14584/// axes conceptually sparse; the others refuse one.
14585fn sparse_form(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14586    if x.rank() != 0 {
14587        return Err(Error::new(ErrorKind::Rank, "a sparse form is one atom", Some(span)));
14588    }
14589    let form = x
14590        .to_i64_vec_near(near)
14591        .and_then(|v| v.first().copied())
14592        .ok_or_else(|| Error::domain("a sparse form is an integer", span))?;
14593    match form {
14594        0 if y.is_sparse() => return Ok(y.densified()),
14595        0 => return crate::sparse::sparsify(y, span),
14596        1 => return crate::sparse::create(y, span),
14597        2 => {
14598            let axes: Vec<i64> = match y.sparse_parts() {
14599                Some(s) => s.axes.iter().map(|&k| k as i64).collect(),
14600                None => (0..y.rank() as i64).collect(),
14601            };
14602            return Ok(Array::from_i64(axes));
14603        }
14604        _ => {}
14605    }
14606    let Some(s) = y.sparse_parts() else {
14607        return Err(Error::domain(
14608            format!("{form} $. reads a sparse array, and this one is dense"),
14609            span,
14610        ));
14611    };
14612    match form {
14613        -1 => Ok(crate::sparse::attributes(y, s)),
14614        3 => Ok(crate::sparse::fill_of(s)),
14615        4 => Ok(crate::sparse::indices_of(s)),
14616        5 => Ok(crate::sparse::values_of(y, s)),
14617        7 => Ok(Array::scalar_i64(s.entries as i64)),
14618        8 => Ok(crate::sparse::compress(y, s)),
14619        _ => Err(Error::domain(format!("{form} is not a sparse form"), span)),
14620    }
14621}
14622
14623/// `L. y`: how deep the boxing goes. Anything unboxed is level 0.
14624fn boxing_level(y: &Array) -> i64 {
14625    match y.as_boxes() {
14626        None => 0,
14627        Some(bs) => 1 + bs.iter().map(boxing_level).max().unwrap_or(0),
14628    }
14629}
14630
14631/// `↓ y`: split — the vectors along the last axis, each enclosed, laid out
14632/// in the shape the remaining axes give. GNU APL has no monadic `↓`; this
14633/// follows Dyalog's published definition.
14634fn split_items(y: &Array) -> Array {
14635    if y.rank() == 0 {
14636        return Array::boxed(y.clone());
14637    }
14638    let last = y.shape[y.rank() - 1];
14639    let outer: Vec<usize> = y.shape[..y.rank() - 1].to_vec();
14640    let n: usize = outer.iter().product();
14641    let mut boxes = Vec::with_capacity(n);
14642    for i in 0..n {
14643        let mut data = Data::empty(y.dtype());
14644        for k in 0..last {
14645            push_elem(&mut data, &y.data, i * last + k);
14646        }
14647        boxes.push(Array::new(vec![last], data));
14648    }
14649    Array::new(outer, Data::Box(boxes.into()))
14650}
14651
14652/// `x ⊃ y`: pick. Each item of x is one step of a path — a boxed step is a
14653/// whole coordinate vector, a simple one indexes the items.
14654fn pick(x: &Array, y: &Array, origin: i64, near: NearInt, span: Span) -> Result<Array> {
14655    let xs = as_list(x);
14656    let mut cur = y.clone();
14657    for i in 0..xs.items() {
14658        let step = open_cell(&item_or_self(&xs, i));
14659        let idx = step
14660            .to_i64_vec_near(near)
14661            .ok_or_else(|| Error::domain("a pick path holds integers", span))?;
14662        let base =
14663            if cur.rank() == 0 { Array::new(vec![1], cur.data.clone()) } else { cur.clone() };
14664        if idx.len() > base.rank() {
14665            return Err(Error::new(
14666                ErrorKind::Length,
14667                format!(
14668                    "a path step of {} index(es) into a value of rank {}",
14669                    idx.len(),
14670                    cur.rank()
14671                ),
14672                Some(span),
14673            ));
14674        }
14675        let zeroed: Vec<i64> = idx.iter().map(|&v| v - origin).collect();
14676        let at = cell_index(&base, &zeroed, span)?;
14677        cur = open_cell(&base.cell_at(idx.len(), at));
14678    }
14679    Ok(cur)
14680}
14681
14682// ------------------------------------------------------------------ primes
14683
14684/// `x p: y`: the facts about primes J spells with this conjunction of
14685/// arguments. Every form here reads one integer and answers about it.
14686fn prime_meta(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14687    let form = one_int(x, "a prime query", near, span)?;
14688    let n = one_int(y, "a prime query", near, span)?;
14689    match form {
14690        // How many primes are below y.
14691        -1 => Ok(Array::scalar_i64(primes_below(n, span)?)),
14692        // Whether y is prime, and its negation.
14693        0 => Ok(Array::scalar_bool(!is_prime(n))),
14694        1 => Ok(Array::scalar_bool(is_prime(n))),
14695        // The factorisation as a table, and its top row on its own.
14696        2 | 3 => {
14697            let (ps, es) = factor_table(n, span)?;
14698            let k = ps.len();
14699            if form == 3 {
14700                return Ok(Array::from_i64(ps));
14701            }
14702            let mut all = ps;
14703            all.extend(es);
14704            Ok(Array::new(vec![2, k], Data::I64(all.into())))
14705        }
14706        // The neighbouring primes.
14707        4 => Ok(Array::scalar_i64(next_prime(n, span)?)),
14708        -4 => Ok(Array::scalar_i64(previous_prime(n, span)?)),
14709        other => Err(Error::domain(format!("{other} is not a prime query"), span)),
14710    }
14711}
14712
14713/// `x q: y`: the exponents of the primes in y — of the first x of them, or,
14714/// for `__`, of the ones that actually divide y over a second row.
14715fn prime_exponents(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14716    let n = one_int(y, "prime exponents", near, span)?;
14717    let count = x.to_f64_vec().and_then(|v| v.first().copied()).unwrap_or(0.0);
14718    let (ps, es) = factor_table(n, span)?;
14719    if count == f64::NEG_INFINITY {
14720        let k = ps.len();
14721        let mut all = ps;
14722        all.extend(es);
14723        return Ok(Array::new(vec![2, k], Data::I64(all.into())));
14724    }
14725    let want = one_int(x, "prime exponents", near, span)?;
14726    if want < 0 {
14727        return Err(Error::not_yet(format!("the prime exponent form ({want} q:)"), span));
14728    }
14729    let mut out = Vec::with_capacity(want as usize);
14730    for i in 0..want {
14731        let p = nth_prime(i, span)?;
14732        out.push(ps.iter().position(|&q| q == p).map_or(0, |at| es[at]));
14733    }
14734    Ok(Array::from_i64(out))
14735}
14736
14737/// y's distinct prime factors, ascending, and how often each divides it.
14738fn factor_table(n: i64, span: Span) -> Result<(Vec<i64>, Vec<i64>)> {
14739    let factors = prime_factors(n, span)?;
14740    let mut ps: Vec<i64> = Vec::new();
14741    let mut es: Vec<i64> = Vec::new();
14742    for f in factors {
14743        if ps.last() == Some(&f) {
14744            *es.last_mut().unwrap() += 1;
14745        } else {
14746            ps.push(f);
14747            es.push(1);
14748        }
14749    }
14750    Ok((ps, es))
14751}
14752
14753fn is_prime(n: i64) -> bool {
14754    if n < 2 {
14755        return false;
14756    }
14757    let mut d = 2i64;
14758    while d.saturating_mul(d) <= n {
14759        if n % d == 0 {
14760            return false;
14761        }
14762        d += 1;
14763    }
14764    true
14765}
14766
14767fn primes_below(n: i64, span: Span) -> Result<i64> {
14768    if n < 0 {
14769        return Err(Error::domain("counting the primes below a negative number", span));
14770    }
14771    Ok((2..n).filter(|&k| is_prime(k)).count() as i64)
14772}
14773
14774fn next_prime(n: i64, span: Span) -> Result<i64> {
14775    let mut k = n.checked_add(1).ok_or_else(|| Error::domain("no next prime", span))?;
14776    while !is_prime(k) {
14777        k = k.checked_add(1).ok_or_else(|| Error::domain("no next prime", span))?;
14778    }
14779    Ok(k)
14780}
14781
14782fn previous_prime(n: i64, span: Span) -> Result<i64> {
14783    let mut k = n - 1;
14784    while k >= 2 {
14785        if is_prime(k) {
14786            return Ok(k);
14787        }
14788        k -= 1;
14789    }
14790    Err(Error::domain(format!("there is no prime below {n}"), span))
14791}
14792
14793/// One whole number from an argument that has to hold exactly that.
14794fn one_int(a: &Array, what: &str, near: NearInt, span: Span) -> Result<i64> {
14795    a.to_i64_vec_near(near)
14796        .and_then(|v| v.first().copied())
14797        .ok_or_else(|| Error::domain(format!("{what} needs an integer"), span))
14798}
14799
14800/// `x \\ y`: expand. Every 1 in x takes the next item of y; every 0 leaves
14801/// a fill in its place — the type's own fill, or, for a nested argument in
14802/// APL, the prototype of its first item.
14803fn expand(x: &Array, y: &Array, apl: bool, near: NearInt, span: Span) -> Result<Array> {
14804    let mask = x
14805        .to_i64_vec_near(near)
14806        .ok_or_else(|| Error::domain("an expansion mask holds 0s and 1s", span))?;
14807    if mask.iter().any(|&b| b != 0 && b != 1) {
14808        return Err(Error::domain("an expansion mask holds 0s and 1s", span));
14809    }
14810    let ys = as_list(y);
14811    let taken = mask.iter().filter(|&&b| b == 1).count();
14812    let n = ys.items();
14813    // A one-item argument spreads over every slot the mask opens.
14814    let spread = n == 1 && taken != 1;
14815    if !spread && taken != n {
14816        return Err(Error::new(
14817            ErrorKind::Length,
14818            format!("an expansion mask taking {taken} item(s) over {n}"),
14819            Some(span),
14820        ));
14821    }
14822    let m = ys.item_size();
14823    let fill = if apl { prototype_of(&ys) } else { None };
14824    let mut data = Data::empty(ys.dtype());
14825    let mut at = 0usize;
14826    for &b in &mask {
14827        if b == 1 {
14828            let from = if spread { 0 } else { at };
14829            for k in 0..m {
14830                push_elem(&mut data, &ys.data, from * m + k);
14831            }
14832            at += 1;
14833        } else {
14834            for _ in 0..m {
14835                push_gap(&mut data, &fill);
14836            }
14837        }
14838    }
14839    let mut shape = ys.shape.clone();
14840    if shape.is_empty() {
14841        shape.push(mask.len());
14842    } else {
14843        shape[0] = mask.len();
14844    }
14845    Ok(keep_proto(Array::new(shape, data), &ys, apl))
14846}
14847
14848/// `". y` and `⍎ y`: the characters of y as a program of this language,
14849/// compiled now and run here.
14850///
14851/// The nested program shares the caller's names and its output sink, which
14852/// is what makes `". 'a =. 3'` assign in the scope the sentence stands in.
14853/// It reaches nothing the caller could not reach: the sandbox contract is
14854/// about what a primitive may touch, and evaluation touches nothing new.
14855fn execute(y: &Array, apl: bool, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
14856    // An argument with no elements is the empty program, whatever type it
14857    // was going to hold — there is no character in it to refuse. J answers
14858    // the empty program with an empty value; APL's answers nothing at all,
14859    // which every caller of a verb here has to report as a refusal.
14860    if y.count() == 0 {
14861        if apl {
14862            return execute_source("", apl, ctx, span);
14863        }
14864        return Ok(Array::new(y.shape.clone(), Data::empty(DType::Bool)));
14865    }
14866    let Data::Char(v) = &y.data else {
14867        return Err(Error::domain("execute reads a character list", span));
14868    };
14869    let src: String = v.iter().collect();
14870    execute_source(&src, apl, ctx, span)
14871}
14872
14873/// [`execute`] over source that is already text: APL's `⎕` reads a line and
14874/// runs it, which is execute over a string nobody boxed into an array.
14875pub(crate) fn execute_source(
14876    src: &str,
14877    apl: bool,
14878    ctx: &mut Ctx<'_>,
14879    span: Span,
14880) -> Result<Array> {
14881    let lang = if apl { crate::Lang::Apl } else { crate::Lang::J };
14882    // The nested program runs under the dialect the caller was compiled
14883    // with — every setting of it, not the index origin alone.
14884    let dialect = ctx.cfg.rules.dialect();
14885    let nested = crate::compile(lang, src, &dialect).map_err(|e| nested_error(e, src, span))?;
14886    if !nested.params.is_empty() {
14887        return Err(Error::domain(
14888            "an executed string cannot take host data: `{name}` has nothing to bind to",
14889            span,
14890        ));
14891    }
14892    let mut rec = None;
14893    let (value, _) = crate::ir::run_block(&nested.stmts, None, ctx, &mut rec)
14894        .map_err(|e| nested_error(e, src, span))?;
14895    value.ok_or_else(|| Error::domain("the executed string yielded no value", span))
14896}
14897
14898/// The stream number a J file foreign was given, checked against the one
14899/// the sandbox opens for that direction.
14900///
14901/// J numbers its streams and its open files alike, so a number that is not
14902/// the standard one is a file handle; a boxed argument is a file NAME. Both
14903/// are the filesystem, which the sandbox closes.
14904fn stream_number(y: &Array, open: i64, what: &str, span: Span) -> Result<()> {
14905    let closed = || {
14906        Err(Error::sandbox(
14907            format!("{what} the standard stream {open} only; a file is outside the program"),
14908            span,
14909        ))
14910    };
14911    if matches!(y.data, Data::Box(_)) {
14912        return closed();
14913    }
14914    match y.to_i64_vec().as_deref() {
14915        Some([n]) if *n == open => Ok(()),
14916        Some([_]) => closed(),
14917        _ => Err(Error::domain(format!("{what} one stream number"), span)),
14918    }
14919}
14920
14921/// `3!:0 y`: the code J gives y's element type. The numbers are J's own,
14922/// and libjay's element types line up with them one for one.
14923/// J's code for the argument's element type. A sparse array has a code of
14924/// its own for every element type that can be stored sparsely, one factor
14925/// of 1024 above the dense one.
14926fn type_code(y: &Array) -> i64 {
14927    if y.is_sparse() {
14928        return 1024 * dense_type_code(y);
14929    }
14930    dense_type_code(y)
14931}
14932
14933fn dense_type_code(y: &Array) -> i64 {
14934    match y.dtype() {
14935        DType::Bool => 1,
14936        DType::Char => 2,
14937        DType::I64 => 4,
14938        DType::F64 => 8,
14939        DType::Complex => 16,
14940        DType::Box => 32,
14941        DType::Ext => 64,
14942        DType::Rat => 128,
14943        DType::Symbol => 65536,
14944    }
14945}
14946
14947/// An error from an executed string, re-pointed at the sentence that ran it.
14948/// The inner diagnostic still reads in full, as a note, because its spans
14949/// point into a source the caller never sees.
14950fn nested_error(e: Error, src: &str, span: Span) -> Error {
14951    let inner = e.render(src);
14952    let mut out = Error::new(e.kind, format!("in the executed string: {}", e.msg), Some(span));
14953    out.notes.push(inner.trim_end().to_string());
14954    out
14955}
14956
14957// ------------------------------------------------------------------- words
14958
14959/// `;: y`: J's own word rules over a character list, each word a box. A run
14960/// of numeric literals separated by blanks is one word, which is what makes
14961/// `'1 2 3'` a single number and `'i.5'` two words.
14962fn words(y: &Array, span: Span) -> Result<Array> {
14963    // Nothing to read is no word, whatever type the empty was going to
14964    // hold: `;: (0$1 2 3)` is the empty list of boxes.
14965    if y.count() == 0 {
14966        return Ok(Array::new(vec![0], Data::Box(Vec::new().into())));
14967    }
14968    let Data::Char(v) = &y.data else {
14969        return Err(Error::domain("words reads a character list", span));
14970    };
14971    let src: Vec<char> = v.as_slice().to_vec();
14972    let n = src.len();
14973    let mut out: Vec<Array> = Vec::new();
14974    let mut i = 0usize;
14975    let numeric_start = |k: usize| -> bool {
14976        k < n && (src[k].is_ascii_digit() || src[k] == '_')
14977    };
14978    while i < n {
14979        let c = src[i];
14980        if c == ' ' || c == '\t' {
14981            i += 1;
14982            continue;
14983        }
14984        let start = i;
14985        if c == '\'' {
14986            i += 1;
14987            loop {
14988                if i >= n {
14989                    return Err(Error::parse("a word list ends inside a string", span));
14990                }
14991                if src[i] == '\'' {
14992                    i += 1;
14993                    if i < n && src[i] == '\'' {
14994                        i += 1;
14995                        continue;
14996                    }
14997                    break;
14998                }
14999                i += 1;
15000            }
15001        } else if c.is_ascii_alphabetic() {
15002            while i < n && (src[i].is_ascii_alphanumeric() || src[i] == '_') {
15003                i += 1;
15004            }
15005            if i < n && (src[i] == '.' || src[i] == ':') {
15006                i += 1;
15007            }
15008            // `NB.` swallows the rest of the line, comment and all.
15009            if src[start..i].iter().collect::<String>() == "NB." {
15010                while i < n && src[i] != '\n' {
15011                    i += 1;
15012                }
15013            }
15014        } else if numeric_start(i) {
15015            loop {
15016                while i < n && (src[i].is_ascii_alphanumeric() || src[i] == '.' || src[i] == '_')
15017                {
15018                    i += 1;
15019                }
15020                // A blank between two numeric literals keeps one word.
15021                let mut j = i;
15022                while j < n && src[j] == ' ' {
15023                    j += 1;
15024                }
15025                if j > i && numeric_start(j) {
15026                    i = j;
15027                    continue;
15028                }
15029                break;
15030            }
15031        } else {
15032            i += 1;
15033            while i < n && (src[i] == '.' || src[i] == ':') {
15034                i += 1;
15035            }
15036        }
15037        out.push(Array::from_chars(src[start..i].to_vec()));
15038    }
15039    let k = out.len();
15040    Ok(Array::new(vec![k], Data::Box(out.into())))
15041}
15042
15043#[cfg(test)]
15044mod tests {
15045    use super::*;
15046
15047    /// A context bound to a discarding output sink.
15048    macro_rules! ctx {
15049        ($name:ident, $agreement:expr) => {
15050            let mut sink = |_: &str| {};
15051            let mut env = Env::new(Vec::new());
15052            #[allow(unused_mut)]
15053            let mut $name = Ctx {
15054                cfg: EvalCfg {
15055                    agreement: $agreement,
15056                    fmt: FmtOpts::J,
15057                    tol: Tol::J,
15058                    // The agreement names the language here, so the rules
15059                    // a verb reads are that language's shipped dialect.
15060                    rules: crate::frontend::Dialect::default()
15061                        .rules(if $agreement == Agreement::ExactOrScalar {
15062                            crate::Lang::Apl
15063                        } else {
15064                            crate::Lang::J
15065                        })
15066                        .expect("the shipped dialect is implemented"),
15067                },
15068                out: &mut sink,
15069                inp: None,
15070                env: &mut env,
15071                device: None,
15072            };
15073        };
15074        ($name:ident) => {
15075            ctx!($name, Agreement::LeadingPrefix);
15076        };
15077    }
15078
15079    fn scalar_prim(name: &'static str, monad: MonadOp, dyad: DyadOp) -> Verb {
15080        Verb::Prim(Prim { name, monad, dyad, ranks: [0, 0, 0] })
15081    }
15082
15083    fn inf_prim(name: &'static str, monad: MonadOp, dyad: DyadOp) -> Verb {
15084        Verb::Prim(Prim { name, monad, dyad, ranks: [RANK_INF, RANK_INF, RANK_INF] })
15085    }
15086
15087    fn plus() -> Verb {
15088        scalar_prim("+", MonadOp::Scalar(ScalarMonad::Conj), DyadOp::Scalar(ScalarDyad::Add))
15089    }
15090    fn minus() -> Verb {
15091        scalar_prim("-", MonadOp::Scalar(ScalarMonad::Neg), DyadOp::Scalar(ScalarDyad::Sub))
15092    }
15093    fn times() -> Verb {
15094        scalar_prim("*", MonadOp::Scalar(ScalarMonad::Signum), DyadOp::Scalar(ScalarDyad::Mul))
15095    }
15096    fn pct() -> Verb {
15097        scalar_prim("%", MonadOp::Scalar(ScalarMonad::Recip), DyadOp::Scalar(ScalarDyad::DivJ))
15098    }
15099    fn div_apl() -> Verb {
15100        scalar_prim("÷", MonadOp::Scalar(ScalarMonad::Recip), DyadOp::Scalar(ScalarDyad::DivApl))
15101    }
15102    fn floor_v() -> Verb {
15103        scalar_prim("<.", MonadOp::Scalar(ScalarMonad::Floor), DyadOp::Scalar(ScalarDyad::Min))
15104    }
15105    fn ceil_v() -> Verb {
15106        scalar_prim(">.", MonadOp::Scalar(ScalarMonad::Ceil), DyadOp::Scalar(ScalarDyad::Max))
15107    }
15108    fn pow_v() -> Verb {
15109        scalar_prim("^", MonadOp::Scalar(ScalarMonad::Exp), DyadOp::Scalar(ScalarDyad::Pow))
15110    }
15111    fn residue_v() -> Verb {
15112        scalar_prim("|", MonadOp::Scalar(ScalarMonad::Abs), DyadOp::Scalar(ScalarDyad::Residue))
15113    }
15114    fn eq_v() -> Verb {
15115        scalar_prim("=", MonadOp::None, DyadOp::Scalar(ScalarDyad::Eq))
15116    }
15117    fn lt_v() -> Verb {
15118        scalar_prim("<", MonadOp::None, DyadOp::Scalar(ScalarDyad::Lt))
15119    }
15120    fn not_v() -> Verb {
15121        scalar_prim("-.", MonadOp::Scalar(ScalarMonad::Not), DyadOp::None)
15122    }
15123    fn sqrt_v() -> Verb {
15124        scalar_prim("%:", MonadOp::Scalar(ScalarMonad::Sqrt), DyadOp::NotYet("dyadic root"))
15125    }
15126    fn dollar() -> Verb {
15127        inf_prim("$", MonadOp::ShapeOf, DyadOp::Reshape)
15128    }
15129    fn pound() -> Verb {
15130        inf_prim("#", MonadOp::Tally, DyadOp::NotYet("copy"))
15131    }
15132    fn comma() -> Verb {
15133        inf_prim(",", MonadOp::Ravel, DyadOp::NotYet("append"))
15134    }
15135    fn transpose_v() -> Verb {
15136        inf_prim("|:", MonadOp::TransposeAxes, DyadOp::NotYet("dyadic transpose"))
15137    }
15138    fn head_v() -> Verb {
15139        inf_prim("{.", MonadOp::Head, DyadOp::Take)
15140    }
15141    fn behead_v() -> Verb {
15142        inf_prim("}.", MonadOp::Behead, DyadOp::Drop)
15143    }
15144    fn iota() -> Verb {
15145        inf_prim("i.", MonadOp::IotaJ, DyadOp::NotYet("index of"))
15146    }
15147    fn iota_apl(origin: i64) -> Verb {
15148        inf_prim("⍳", MonadOp::IotaApl { origin }, DyadOp::NotYet("index of"))
15149    }
15150    fn right_v() -> Verb {
15151        inf_prim("]", MonadOp::Same, DyadOp::Right)
15152    }
15153    fn echo_v() -> Verb {
15154        inf_prim("echo", MonadOp::Echo, DyadOp::None)
15155    }
15156
15157    fn b(v: Verb) -> Box<Verb> {
15158        Box::new(v)
15159    }
15160
15161    fn mat(rows: usize, cols: usize, v: Vec<i64>) -> Array {
15162        Array::new(vec![rows, cols], Data::I64(v.into()))
15163    }
15164
15165    /// The elements in reading order, whatever layout the result kept.
15166    fn ints(a: &Array) -> Vec<i64> {
15167        a.to_row_major().as_i64_slice().expect("integer result").to_vec()
15168    }
15169
15170    fn floats(a: &Array) -> Vec<f64> {
15171        a.to_row_major().as_f64_slice().expect("float result").to_vec()
15172    }
15173
15174    fn bools(a: &Array) -> Vec<u8> {
15175        match &a.to_row_major().data {
15176            Data::Bool(v) => v.to_vec(),
15177            other => panic!("expected boolean result, got {other:?}"),
15178        }
15179    }
15180
15181    fn sp() -> Span {
15182        Span::new(0, 1)
15183    }
15184
15185    fn close(a: f64, b: f64) -> bool {
15186        (a - b).abs() < 1e-9 || (a.is_infinite() && b.is_infinite() && a.signum() == b.signum())
15187    }
15188
15189    // ------------------------------------------------------------- naming
15190
15191    #[test]
15192    fn names_of_primitives_and_derived_verbs() {
15193        assert_eq!(plus().name(), "+");
15194        assert_eq!(Verb::Rank(b(plus()), [1, 1, 1]).name(), "+\"1");
15195        assert_eq!(Verb::Rank(b(plus()), [0, 1, RANK_INF]).name(), "+\"0 1 _");
15196        assert_eq!(Verb::Rank(b(plus()), [RANK_INF; 3]).name(), "+\"_");
15197        assert_eq!(Verb::Reduce(b(plus())).name(), "+/");
15198        assert_eq!(Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]).name(), "+/\"1");
15199        assert_eq!(Verb::Fork(b(plus()), b(minus()), b(times())).name(), "(+ - *)");
15200        assert_eq!(
15201            Verb::NounFork(Array::scalar_i64(1), b(plus()), b(minus())).name(),
15202            "(n + -)"
15203        );
15204        assert_eq!(Verb::Hook(b(plus()), b(minus())).name(), "(+ -)");
15205        assert_eq!(Verb::Atop(b(plus()), b(minus())).name(), "(+@:-)");
15206        assert_eq!(Verb::Compose(b(plus()), b(minus())).name(), "(+&:-)");
15207        assert_eq!(Verb::BondLeft(Array::scalar_i64(1), b(plus())).name(), "(n&+)");
15208        assert_eq!(Verb::BondRight(b(plus()), Array::scalar_i64(1)).name(), "(+&n)");
15209    }
15210
15211    #[test]
15212    fn composition_applies_the_right_verb_to_both_arguments() {
15213        ctx!(c);
15214        let v = Verb::Compose(b(plus()), b(times()));
15215        // Monadically an atop; dyadically the right verb runs on each side.
15216        let r = v.monad(&Array::from_i64(vec![-2, 0, 3]), &mut c, sp()).unwrap();
15217        assert_eq!(ints(&r), vec![-1, 0, 1]);
15218        let r = v
15219            .dyad(&Array::scalar_i64(-5), &Array::scalar_i64(7), &mut c, sp())
15220            .unwrap();
15221        assert_eq!(ints(&r), vec![0]);
15222        // A bond has a monadic valence only.
15223        let bond = Verb::BondLeft(Array::scalar_i64(10), b(minus()));
15224        let r = bond.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
15225        assert_eq!(ints(&r), vec![9, 8]);
15226        let e = bond
15227            .dyad(&Array::scalar_i64(1), &Array::scalar_i64(2), &mut c, sp())
15228            .unwrap_err();
15229        assert_eq!(e.kind, ErrorKind::Domain);
15230        let bond = Verb::BondRight(b(minus()), Array::scalar_i64(10));
15231        let r = bond.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
15232        assert_eq!(ints(&r), vec![-9, -8]);
15233    }
15234
15235    // ------------------------------------------------- rank and agreement
15236
15237    #[test]
15238    fn scalar_monad_covers_the_whole_buffer() {
15239        ctx!(c);
15240        let r = minus().monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
15241        assert_eq!(r.shape, vec![2, 3]);
15242        assert_eq!(ints(&r), vec![-1, -2, -3, -4, -5, -6]);
15243    }
15244
15245    #[test]
15246    fn leading_prefix_agreement_broadcasts_per_row() {
15247        ctx!(c);
15248        let x = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15249        let y = Array::from_i64(vec![10, 20]);
15250        let r = plus().dyad(&x, &y, &mut c, sp()).unwrap();
15251        assert_eq!(r.shape, vec![2, 3]);
15252        assert_eq!(ints(&r), vec![11, 12, 13, 24, 25, 26]);
15253        // and the same pairing with the operands swapped
15254        let r = plus().dyad(&y, &x, &mut c, sp()).unwrap();
15255        assert_eq!(ints(&r), vec![11, 12, 13, 24, 25, 26]);
15256    }
15257
15258    #[test]
15259    fn exact_or_scalar_rejects_a_prefix_frame() {
15260        ctx!(c, Agreement::ExactOrScalar);
15261        let x = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15262        let y = Array::from_i64(vec![10, 20]);
15263        let e = plus().dyad(&x, &y, &mut c, sp()).unwrap_err();
15264        assert_eq!(e.kind, ErrorKind::Shape);
15265        assert!(e.msg.contains("2 3"), "{}", e.msg);
15266        assert!(e.msg.contains("right shape 2"), "{}", e.msg);
15267    }
15268
15269    #[test]
15270    fn exact_or_scalar_accepts_equal_frames_and_scalars() {
15271        ctx!(c, Agreement::ExactOrScalar);
15272        let x = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15273        let r = plus().dyad(&x, &x, &mut c, sp()).unwrap();
15274        assert_eq!(ints(&r), vec![2, 4, 6, 8, 10, 12]);
15275        let r = plus().dyad(&Array::scalar_i64(10), &x, &mut c, sp()).unwrap();
15276        assert_eq!(r.shape, vec![2, 3]);
15277        assert_eq!(ints(&r), vec![11, 12, 13, 14, 15, 16]);
15278        let r = plus().dyad(&x, &Array::scalar_i64(10), &mut c, sp()).unwrap();
15279        assert_eq!(ints(&r), vec![11, 12, 13, 14, 15, 16]);
15280    }
15281
15282    #[test]
15283    fn vector_length_mismatch_is_a_length_error() {
15284        ctx!(c);
15285        let e = plus()
15286            .dyad(&Array::from_i64(vec![1, 2, 3]), &Array::from_i64(vec![1, 2, 3, 4, 5]), &mut c, sp())
15287            .unwrap_err();
15288        assert_eq!(e.kind, ErrorKind::Length);
15289        assert!(e.msg.contains("left shape 3"), "{}", e.msg);
15290        assert!(e.msg.contains("right shape 5"), "{}", e.msg);
15291        assert!(e.notes[0].contains("axis 0"), "{:?}", e.notes);
15292    }
15293
15294    #[test]
15295    fn diverging_matrix_frames_name_the_axis() {
15296        ctx!(c);
15297        let e = plus()
15298            .dyad(&mat(2, 3, vec![0; 6]), &mat(2, 4, vec![0; 8]), &mut c, sp())
15299            .unwrap_err();
15300        assert_eq!(e.kind, ErrorKind::Shape);
15301        assert!(e.notes[0].contains("axis 1"), "{:?}", e.notes);
15302    }
15303
15304    #[test]
15305    fn dyadic_rank_pairs_rows_with_the_whole_right_argument() {
15306        ctx!(c);
15307        // Left cells are rows, the right argument is one cell for all of them.
15308        let v = Verb::Rank(b(plus()), [0, 1, 1]);
15309        let r = v
15310            .dyad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &Array::from_i64(vec![10, 20, 30]), &mut c, sp())
15311            .unwrap();
15312        assert_eq!(r.shape, vec![2, 3]);
15313        assert_eq!(ints(&r), vec![11, 22, 33, 14, 25, 36]);
15314    }
15315
15316    #[test]
15317    fn surplus_frame_axes_repeat_the_shorter_frames_cells() {
15318        ctx!(c);
15319        // Left cells are scalars (frame 2 2), right cells are rows (frame 2):
15320        // each right row serves the two left cells sharing its index.
15321        let v = Verb::Rank(b(head_v()), [0, 0, 1]);
15322        let x = mat(2, 2, vec![1, 1, 2, 2]);
15323        let y = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15324        let r = v.dyad(&x, &y, &mut c, sp()).unwrap();
15325        assert_eq!(r.shape, vec![2, 2, 2]);
15326        assert_eq!(ints(&r), vec![1, 0, 1, 0, 4, 5, 4, 5]);
15327    }
15328
15329    #[test]
15330    fn an_empty_frame_pairs_its_single_cell_with_every_other_cell() {
15331        ctx!(c, Agreement::ExactOrScalar);
15332        // Right cell rank 1 leaves an empty right frame; the left frame is 2.
15333        let v = Verb::Rank(b(head_v()), [0, 0, 1]);
15334        let x = Array::from_i64(vec![1, 2]);
15335        let y = Array::from_i64(vec![7, 8, 9]);
15336        let r = v.dyad(&x, &y, &mut c, sp()).unwrap();
15337        assert_eq!(r.shape, vec![2, 2]);
15338        assert_eq!(ints(&r), vec![7, 0, 7, 8]);
15339    }
15340
15341    #[test]
15342    fn negative_rank_leaves_frame_axes() {
15343        ctx!(c);
15344        // Rank _1 on a matrix leaves one frame axis: shape of each row.
15345        let v = Verb::Rank(b(dollar()), [-1, -1, -1]);
15346        let r = v.monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
15347        assert_eq!(r.shape, vec![2, 1]);
15348        assert_eq!(ints(&r), vec![3, 3]);
15349    }
15350
15351    #[test]
15352    fn effective_rank_clamps_and_counts_back() {
15353        assert_eq!(effective_rank(0, 3), 0);
15354        assert_eq!(effective_rank(2, 1), 1);
15355        assert_eq!(effective_rank(RANK_INF, 4), 4);
15356        assert_eq!(effective_rank(-1, 3), 2);
15357        assert_eq!(effective_rank(-5, 3), 0);
15358    }
15359
15360    // ---------------------------------------------------------- reduction
15361
15362    #[test]
15363    fn reduction_folds_right_to_left() {
15364        ctx!(c);
15365        // -/ 1 2 3 is 1-(2-3), not (1-2)-3.
15366        let r = Verb::Reduce(b(minus()))
15367            .monad(&Array::from_i64(vec![1, 2, 3]), &mut c, sp())
15368            .unwrap();
15369        assert!(r.shape.is_empty());
15370        assert_eq!(ints(&r), vec![2]);
15371    }
15372
15373    #[test]
15374    fn reduction_of_one_item_and_of_a_scalar() {
15375        ctx!(c);
15376        let r = Verb::Reduce(b(plus()))
15377            .monad(&Array::from_i64(vec![7]), &mut c, sp())
15378            .unwrap();
15379        assert!(r.shape.is_empty());
15380        assert_eq!(ints(&r), vec![7]);
15381        let r = Verb::Reduce(b(plus())).monad(&Array::scalar_i64(7), &mut c, sp()).unwrap();
15382        assert_eq!(ints(&r), vec![7]);
15383    }
15384
15385    #[test]
15386    fn reduction_runs_along_the_leading_axis() {
15387        ctx!(c);
15388        let r = Verb::Reduce(b(plus()))
15389            .monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp())
15390            .unwrap();
15391        assert_eq!(r.shape, vec![3]);
15392        assert_eq!(ints(&r), vec![5, 7, 9]);
15393    }
15394
15395    #[test]
15396    fn rank_wrapped_reduction_sums_the_last_axis() {
15397        ctx!(c);
15398        let v = Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]);
15399        let r = v.monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
15400        assert_eq!(r.shape, vec![2]);
15401        assert_eq!(ints(&r), vec![6, 15]);
15402    }
15403
15404    #[test]
15405    fn empty_reduction_uses_the_identity_cell() {
15406        ctx!(c);
15407        let empty = Array::new(vec![0, 2], Data::I64(vec![].into()));
15408        let r = Verb::Reduce(b(plus())).monad(&empty, &mut c, sp()).unwrap();
15409        assert_eq!(r.shape, vec![2]);
15410        assert_eq!(ints(&r), vec![0, 0]);
15411        let r = Verb::Reduce(b(times())).monad(&empty, &mut c, sp()).unwrap();
15412        assert_eq!(ints(&r), vec![1, 1]);
15413        let r = Verb::Reduce(b(floor_v())).monad(&empty, &mut c, sp()).unwrap();
15414        assert!(floats(&r).iter().all(|&x| x == f64::INFINITY));
15415        let r = Verb::Reduce(b(ceil_v())).monad(&empty, &mut c, sp()).unwrap();
15416        assert!(floats(&r).iter().all(|&x| x == f64::NEG_INFINITY));
15417        // Subtraction and division have identities too, and a comparison
15418        // has the conventional one both references print.
15419        let r = Verb::Reduce(b(minus())).monad(&empty, &mut c, sp()).unwrap();
15420        assert_eq!(ints(&r), vec![0, 0]);
15421        let r = Verb::Reduce(b(pct())).monad(&empty, &mut c, sp()).unwrap();
15422        assert_eq!(ints(&r), vec![1, 1]);
15423        let r = Verb::Reduce(b(eq_v())).monad(&empty, &mut c, sp()).unwrap();
15424        assert_eq!(bools(&r), vec![1, 1]);
15425        // An empty vector reduces to a scalar identity.
15426        let r = Verb::Reduce(b(plus()))
15427            .monad(&Array::empty(DType::I64), &mut c, sp())
15428            .unwrap();
15429        assert!(r.shape.is_empty());
15430        assert_eq!(ints(&r), vec![0]);
15431    }
15432
15433    #[test]
15434    fn empty_reduction_without_an_identity_is_a_domain_error() {
15435        ctx!(c);
15436        // A derived verb has no identity cell at all; among the primitives
15437        // only the logarithm and the circle functions are left without one,
15438        // which is what both references do.
15439        let v = Verb::Hook(b(plus()), b(minus()));
15440        let e = Verb::Reduce(b(v)).monad(&Array::empty(DType::I64), &mut c, sp()).unwrap_err();
15441        assert_eq!(e.kind, ErrorKind::Domain);
15442        assert!(e.msg.contains("identity"), "{}", e.msg);
15443    }
15444
15445    #[test]
15446    fn reduction_with_a_non_primitive_verb_uses_the_general_fold() {
15447        ctx!(c);
15448        // The hook x (+ -) y is x + (-y), so this folds as 1-(2-3).
15449        let v = Verb::Reduce(b(Verb::Hook(b(plus()), b(minus()))));
15450        let r = v.monad(&Array::from_i64(vec![1, 2, 3]), &mut c, sp()).unwrap();
15451        assert_eq!(ints(&r), vec![2]);
15452    }
15453
15454    #[test]
15455    fn dyadic_reduction_is_the_table() {
15456        ctx!(c);
15457        // `x u/ y` is the table (outer product), not a windowed reduction —
15458        // the windows are `x u\ y`.
15459        let v = Verb::Reduce(b(plus()));
15460        let r = v
15461            .dyad(&Array::scalar_i64(2), &Array::from_i64(vec![1, 2, 3]), &mut c, sp())
15462            .unwrap();
15463        assert_eq!(r.shape, vec![3]);
15464        assert_eq!(ints(&r), vec![3, 4, 5]);
15465        // The cells are the ones the inner verb's ranks ask for, so a scalar
15466        // verb pairs every atom of x with every atom of y.
15467        let r = v
15468            .dyad(&Array::from_i64(vec![1, 2, 3]), &Array::from_i64(vec![10, 20]), &mut c, sp())
15469            .unwrap();
15470        assert_eq!(r.shape, vec![3, 2]);
15471        assert_eq!(ints(&r), vec![11, 21, 12, 22, 13, 23]);
15472        // An infinite-rank verb takes both arguments whole: one application.
15473        let cat = Verb::Reduce(b(inf_prim(",", MonadOp::Ravel, DyadOp::AppendLeading)));
15474        let r = cat
15475            .dyad(&Array::from_i64(vec![1, 2]), &Array::from_i64(vec![3, 4]), &mut c, sp())
15476            .unwrap();
15477        assert_eq!(r.shape, vec![4]);
15478        assert_eq!(ints(&r), vec![1, 2, 3, 4]);
15479    }
15480
15481    // --------------------------------------------------------- arithmetic
15482
15483    #[test]
15484    fn integer_overflow_promotes_the_whole_result_to_float() {
15485        ctx!(c);
15486        let r = plus()
15487            .dyad(&Array::from_i64(vec![1, i64::MAX]), &Array::scalar_i64(1), &mut c, sp())
15488            .unwrap();
15489        assert_eq!(r.dtype(), DType::F64);
15490        let v = floats(&r);
15491        assert!(close(v[0], 2.0));
15492        assert!(close(v[1], i64::MAX as f64 + 1.0));
15493        // Without overflow the result stays integral.
15494        let r = plus()
15495            .dyad(&Array::from_i64(vec![1, 2]), &Array::scalar_i64(1), &mut c, sp())
15496            .unwrap();
15497        assert_eq!(r.dtype(), DType::I64);
15498    }
15499
15500    #[test]
15501    fn reduction_overflow_promotes_too() {
15502        ctx!(c);
15503        let r = Verb::Reduce(b(plus()))
15504            .monad(&Array::from_i64(vec![i64::MAX, i64::MAX]), &mut c, sp())
15505            .unwrap();
15506        assert_eq!(r.dtype(), DType::F64);
15507        assert!(close(floats(&r)[0], 2.0 * i64::MAX as f64));
15508    }
15509
15510    #[test]
15511    fn booleans_widen_to_integers_in_arithmetic() {
15512        ctx!(c);
15513        let bits = Array::new(vec![3], Data::Bool(vec![1, 0, 1].into()));
15514        let r = plus().dyad(&bits, &bits, &mut c, sp()).unwrap();
15515        assert_eq!(r.dtype(), DType::I64);
15516        assert_eq!(ints(&r), vec![2, 0, 2]);
15517    }
15518
15519    #[test]
15520    fn j_division_is_float_and_survives_zero() {
15521        ctx!(c);
15522        let r = pct()
15523            .dyad(&Array::from_i64(vec![1, -1, 0, 6]), &Array::from_i64(vec![0, 0, 0, 4]), &mut c, sp())
15524            .unwrap();
15525        let v = floats(&r);
15526        assert_eq!(v[0], f64::INFINITY);
15527        assert_eq!(v[1], f64::NEG_INFINITY);
15528        assert_eq!(v[2], 0.0);
15529        assert!(close(v[3], 1.5));
15530    }
15531
15532    #[test]
15533    fn apl_division_by_zero_is_a_domain_error_except_zero_by_zero() {
15534        ctx!(c, Agreement::ExactOrScalar);
15535        let r = div_apl()
15536            .dyad(&Array::scalar_i64(0), &Array::scalar_i64(0), &mut c, sp())
15537            .unwrap();
15538        assert!(close(floats(&r)[0], 1.0));
15539        let e = div_apl()
15540            .dyad(&Array::scalar_i64(1), &Array::scalar_i64(0), &mut c, sp())
15541            .unwrap_err();
15542        assert_eq!(e.kind, ErrorKind::Domain);
15543        assert!(e.msg.contains("division by zero"), "{}", e.msg);
15544        let r = div_apl()
15545            .dyad(&Array::scalar_i64(6), &Array::scalar_i64(4), &mut c, sp())
15546            .unwrap();
15547        assert!(close(floats(&r)[0], 1.5));
15548    }
15549
15550    #[test]
15551    fn reciprocal_of_zero_is_infinite() {
15552        ctx!(c);
15553        let r = pct().monad(&Array::from_i64(vec![0, 2]), &mut c, sp()).unwrap();
15554        let v = floats(&r);
15555        assert_eq!(v[0], f64::INFINITY);
15556        assert!(close(v[1], 0.5));
15557    }
15558
15559    #[test]
15560    fn residue_takes_the_sign_of_the_left_argument() {
15561        ctx!(c);
15562        let x = Array::from_i64(vec![3, 3, -3, -3, 0]);
15563        let y = Array::from_i64(vec![5, -5, 5, -5, 5]);
15564        let r = residue_v().dyad(&x, &y, &mut c, sp()).unwrap();
15565        assert_eq!(ints(&r), vec![2, 1, -1, -2, 5]);
15566        // Floats use the same rule via the floor of the quotient.
15567        let r = residue_v()
15568            .dyad(&Array::from_f64(vec![2.5]), &Array::from_f64(vec![7.0]), &mut c, sp())
15569            .unwrap();
15570        assert!(close(floats(&r)[0], 2.0));
15571    }
15572
15573    #[test]
15574    fn power_stays_integral_when_it_can() {
15575        ctx!(c);
15576        let r = pow_v()
15577            .dyad(&Array::from_i64(vec![2, 0, 5]), &Array::from_i64(vec![10, 0, 1]), &mut c, sp())
15578            .unwrap();
15579        assert_eq!(r.dtype(), DType::I64);
15580        assert_eq!(ints(&r), vec![1024, 1, 5]);
15581        // A negative exponent forces the float path for the whole result.
15582        let r = pow_v()
15583            .dyad(&Array::from_i64(vec![2, 4]), &Array::from_i64(vec![-1, 2]), &mut c, sp())
15584            .unwrap();
15585        assert_eq!(r.dtype(), DType::F64);
15586        assert!(close(floats(&r)[0], 0.5));
15587        assert!(close(floats(&r)[1], 16.0));
15588        // Overflow does the same.
15589        let r = pow_v()
15590            .dyad(&Array::scalar_i64(10), &Array::scalar_i64(30), &mut c, sp())
15591            .unwrap();
15592        assert_eq!(r.dtype(), DType::F64);
15593    }
15594
15595    #[test]
15596    fn comparisons_yield_booleans() {
15597        ctx!(c);
15598        let r = lt_v()
15599            .dyad(&Array::from_i64(vec![1, 2, 3]), &Array::scalar_i64(2), &mut c, sp())
15600            .unwrap();
15601        assert_eq!(bools(&r), vec![1, 0, 0]);
15602        let r = eq_v()
15603            .dyad(&Array::from_f64(vec![1.0, 2.0]), &Array::from_i64(vec![1, 3]), &mut c, sp())
15604            .unwrap();
15605        assert_eq!(bools(&r), vec![1, 0]);
15606    }
15607
15608    #[test]
15609    fn characters_compare_but_do_not_add() {
15610        ctx!(c);
15611        let a = Array::from_chars(vec!['a', 'b']);
15612        let bb = Array::from_chars(vec!['a', 'c']);
15613        assert_eq!(bools(&eq_v().dyad(&a, &bb, &mut c, sp()).unwrap()), vec![1, 0]);
15614        let e = plus().dyad(&a, &bb, &mut c, sp()).unwrap_err();
15615        assert_eq!(e.kind, ErrorKind::Type);
15616        assert!(e.msg.contains("characters"), "{}", e.msg);
15617        let e = lt_v().dyad(&a, &bb, &mut c, sp()).unwrap_err();
15618        assert_eq!(e.kind, ErrorKind::Type);
15619        let e = plus().dyad(&a, &Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap_err();
15620        assert_eq!(e.kind, ErrorKind::Type);
15621        assert!(e.msg.contains("character"), "{}", e.msg);
15622        let e = plus().monad(&a, &mut c, sp()).unwrap_err();
15623        assert_eq!(e.kind, ErrorKind::Type);
15624    }
15625
15626    #[test]
15627    fn floor_and_ceiling_return_integers_when_they_fit() {
15628        ctx!(c);
15629        let r = floor_v().monad(&Array::from_f64(vec![1.5, -1.5]), &mut c, sp()).unwrap();
15630        assert_eq!(r.dtype(), DType::I64);
15631        assert_eq!(ints(&r), vec![1, -2]);
15632        let r = ceil_v().monad(&Array::from_f64(vec![1.5, -1.5]), &mut c, sp()).unwrap();
15633        assert_eq!(ints(&r), vec![2, -1]);
15634        // Values outside the integer range stay floating.
15635        let r = floor_v().monad(&Array::from_f64(vec![1e30]), &mut c, sp()).unwrap();
15636        assert_eq!(r.dtype(), DType::F64);
15637        // Integers pass through unchanged.
15638        let r = floor_v().monad(&Array::from_i64(vec![3]), &mut c, sp()).unwrap();
15639        assert_eq!(ints(&r), vec![3]);
15640    }
15641
15642    #[test]
15643    fn logical_negation_needs_zero_or_one() {
15644        ctx!(c);
15645        let r = not_v().monad(&Array::from_i64(vec![0, 1]), &mut c, sp()).unwrap();
15646        assert_eq!(bools(&r), vec![1, 0]);
15647        let e = not_v().monad(&Array::from_i64(vec![2]), &mut c, sp()).unwrap_err();
15648        assert_eq!(e.kind, ErrorKind::Domain);
15649    }
15650
15651    #[test]
15652    fn signum_abs_and_negation_pick_their_types() {
15653        ctx!(c);
15654        let r = times().monad(&Array::from_i64(vec![-3, 0, 9]), &mut c, sp()).unwrap();
15655        assert_eq!(ints(&r), vec![-1, 0, 1]);
15656        let r = times().monad(&Array::from_f64(vec![-3.0, 0.0, 9.0]), &mut c, sp()).unwrap();
15657        assert_eq!(floats(&r), vec![-1.0, 0.0, 1.0]);
15658        let r = residue_v().monad(&Array::from_i64(vec![-3, 3]), &mut c, sp()).unwrap();
15659        assert_eq!(ints(&r), vec![3, 3]);
15660        let bits = Array::new(vec![2], Data::Bool(vec![0, 1].into()));
15661        let r = minus().monad(&bits, &mut c, sp()).unwrap();
15662        assert_eq!(r.dtype(), DType::I64);
15663        assert_eq!(ints(&r), vec![0, -1]);
15664    }
15665
15666    #[test]
15667    fn square_root_of_a_negative_number_is_complex() {
15668        ctx!(c);
15669        let r = sqrt_v().monad(&Array::from_i64(vec![9]), &mut c, sp()).unwrap();
15670        assert!(close(floats(&r)[0], 3.0));
15671        let r = sqrt_v().monad(&Array::from_i64(vec![-4]), &mut c, sp()).unwrap();
15672        assert_eq!(r.dtype(), DType::Complex);
15673        assert_eq!(r.as_complex_slice().expect("complex data"), &[[0.0, 2.0]]);
15674    }
15675
15676    // --------------------------------------------------------- structural
15677
15678    #[test]
15679    fn shape_tally_and_ravel() {
15680        ctx!(c);
15681        let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15682        let r = dollar().monad(&m, &mut c, sp()).unwrap();
15683        assert_eq!(r.shape, vec![2]);
15684        assert_eq!(ints(&r), vec![2, 3]);
15685        let r = pound().monad(&m, &mut c, sp()).unwrap();
15686        assert!(r.shape.is_empty());
15687        assert_eq!(ints(&r), vec![2]);
15688        // A scalar has one item and no axes.
15689        let r = pound().monad(&Array::scalar_i64(5), &mut c, sp()).unwrap();
15690        assert_eq!(ints(&r), vec![1]);
15691        let r = comma().monad(&m, &mut c, sp()).unwrap();
15692        assert_eq!(r.shape, vec![6]);
15693        assert_eq!(ints(&r), vec![1, 2, 3, 4, 5, 6]);
15694    }
15695
15696    #[test]
15697    fn transpose_reverses_the_axes() {
15698        ctx!(c);
15699        let r = transpose_v().monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
15700        assert_eq!(r.shape, vec![3, 2]);
15701        assert_eq!(ints(&r), vec![1, 4, 2, 5, 3, 6]);
15702        // Rank 3: 2 by 1 by 3 becomes 3 by 1 by 2.
15703        let a = Array::new(vec![2, 1, 3], Data::I64(vec![1, 2, 3, 4, 5, 6].into()));
15704        let r = transpose_v().monad(&a, &mut c, sp()).unwrap();
15705        assert_eq!(r.shape, vec![3, 1, 2]);
15706        assert_eq!(ints(&r), vec![1, 4, 2, 5, 3, 6]);
15707        // Vectors and scalars are unchanged.
15708        let v = Array::from_i64(vec![1, 2]);
15709        assert_eq!(transpose_v().monad(&v, &mut c, sp()).unwrap(), v);
15710    }
15711
15712    #[test]
15713    fn head_and_behead() {
15714        ctx!(c);
15715        let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15716        let r = head_v().monad(&m, &mut c, sp()).unwrap();
15717        assert_eq!(r.shape, vec![3]);
15718        assert_eq!(ints(&r), vec![1, 2, 3]);
15719        let r = behead_v().monad(&m, &mut c, sp()).unwrap();
15720        assert_eq!(r.shape, vec![1, 3]);
15721        assert_eq!(ints(&r), vec![4, 5, 6]);
15722        // The head of an empty array is a cell of fills.
15723        let e = Array::new(vec![0, 2], Data::I64(vec![].into()));
15724        let r = head_v().monad(&e, &mut c, sp()).unwrap();
15725        assert_eq!(r.shape, vec![2]);
15726        assert_eq!(ints(&r), vec![0, 0]);
15727        assert_eq!(behead_v().monad(&e, &mut c, sp()).unwrap(), e);
15728        assert_eq!(head_v().monad(&Array::scalar_i64(5), &mut c, sp()).unwrap().shape, Vec::<usize>::new());
15729        let err = behead_v().monad(&Array::scalar_i64(5), &mut c, sp()).unwrap_err();
15730        assert_eq!(err.kind, ErrorKind::Domain);
15731    }
15732
15733    #[test]
15734    fn iota_fills_a_shape_and_reverses_negative_axes() {
15735        ctx!(c);
15736        let r = iota().monad(&Array::from_i64(vec![2, 3]), &mut c, sp()).unwrap();
15737        assert_eq!(r.shape, vec![2, 3]);
15738        assert_eq!(ints(&r), vec![0, 1, 2, 3, 4, 5]);
15739        // A scalar argument gives one axis.
15740        let r = iota().monad(&Array::scalar_i64(3), &mut c, sp()).unwrap();
15741        assert_eq!(r.shape, vec![3]);
15742        assert_eq!(ints(&r), vec![0, 1, 2]);
15743        // Negative lengths run the axis backwards.
15744        let r = iota().monad(&Array::scalar_i64(-3), &mut c, sp()).unwrap();
15745        assert_eq!(ints(&r), vec![2, 1, 0]);
15746        let r = iota().monad(&Array::from_i64(vec![2, -3]), &mut c, sp()).unwrap();
15747        assert_eq!(r.shape, vec![2, 3]);
15748        assert_eq!(ints(&r), vec![2, 1, 0, 5, 4, 3]);
15749        let r = iota().monad(&Array::from_i64(vec![-2, 3]), &mut c, sp()).unwrap();
15750        assert_eq!(ints(&r), vec![3, 4, 5, 0, 1, 2]);
15751        // Zero lengths give an empty result of that shape.
15752        let r = iota().monad(&Array::scalar_i64(0), &mut c, sp()).unwrap();
15753        assert_eq!(r.shape, vec![0]);
15754        assert!(ints(&r).is_empty());
15755        // Non-integers and matrices are refused.
15756        let e = iota().monad(&Array::from_f64(vec![1.5]), &mut c, sp()).unwrap_err();
15757        assert_eq!(e.kind, ErrorKind::Domain);
15758        let e = iota().monad(&mat(1, 1, vec![1]), &mut c, sp()).unwrap_err();
15759        assert_eq!(e.kind, ErrorKind::Rank);
15760    }
15761
15762    #[test]
15763    fn apl_iota_starts_at_the_index_origin() {
15764        ctx!(c, Agreement::ExactOrScalar);
15765        let r = iota_apl(1).monad(&Array::scalar_i64(3), &mut c, sp()).unwrap();
15766        assert_eq!(ints(&r), vec![1, 2, 3]);
15767        let r = iota_apl(0).monad(&Array::scalar_i64(3), &mut c, sp()).unwrap();
15768        assert_eq!(ints(&r), vec![0, 1, 2]);
15769        let e = iota_apl(1).monad(&Array::scalar_i64(-1), &mut c, sp()).unwrap_err();
15770        assert_eq!(e.kind, ErrorKind::Domain);
15771        // A vector of lengths asks for an array of index vectors, one per
15772        // cell of the result.
15773        let r = iota_apl(1).monad(&Array::from_i64(vec![2, 3]), &mut c, sp()).unwrap();
15774        assert_eq!(r.shape, vec![2, 3]);
15775        assert_eq!(ints(&r.as_boxes().expect("boxed")[4]), vec![2, 2]);
15776    }
15777
15778    #[test]
15779    fn reshape_cycles_the_ravel() {
15780        ctx!(c);
15781        let r = dollar()
15782            .dyad(&Array::from_i64(vec![2, 3]), &Array::from_i64(vec![1, 2]), &mut c, sp())
15783            .unwrap();
15784        assert_eq!(r.shape, vec![2, 3]);
15785        assert_eq!(ints(&r), vec![1, 2, 1, 2, 1, 2]);
15786        // A scalar left argument reshapes to a vector.
15787        let r = dollar()
15788            .dyad(&Array::scalar_i64(3), &Array::from_i64(vec![7]), &mut c, sp())
15789            .unwrap();
15790        assert_eq!(r.shape, vec![3]);
15791        assert_eq!(ints(&r), vec![7, 7, 7]);
15792        // Reshaping down keeps the leading elements, and the type is y's.
15793        let r = dollar()
15794            .dyad(&Array::scalar_i64(2), &Array::from_chars(vec!['a', 'b', 'c']), &mut c, sp())
15795            .unwrap();
15796        assert_eq!(r.dtype(), DType::Char);
15797        // An empty right argument cannot fill a non-empty shape.
15798        let e = dollar()
15799            .dyad(&Array::scalar_i64(2), &Array::empty(DType::I64), &mut c, sp())
15800            .unwrap_err();
15801        assert_eq!(e.kind, ErrorKind::Length);
15802        assert!(e.msg.contains("empty"), "{}", e.msg);
15803        // but an empty shape is fine.
15804        let r = dollar()
15805            .dyad(&Array::scalar_i64(0), &Array::empty(DType::I64), &mut c, sp())
15806            .unwrap();
15807        assert_eq!(r.shape, vec![0]);
15808        let e = dollar()
15809            .dyad(&Array::scalar_i64(-1), &Array::from_i64(vec![1]), &mut c, sp())
15810            .unwrap_err();
15811        assert_eq!(e.kind, ErrorKind::Domain);
15812    }
15813
15814    #[test]
15815    fn take_from_both_ends_and_beyond() {
15816        ctx!(c);
15817        let v = Array::from_i64(vec![1, 2, 3, 4]);
15818        let take = |x: Array, y: &Array, c: &mut Ctx<'_>| head_v().dyad(&x, y, c, sp()).unwrap();
15819        assert_eq!(ints(&take(Array::scalar_i64(2), &v, &mut c)), vec![1, 2]);
15820        assert_eq!(ints(&take(Array::scalar_i64(-2), &v, &mut c)), vec![3, 4]);
15821        // Overtaking pads at the back for a positive count,
15822        let short = Array::from_i64(vec![1, 2, 3]);
15823        assert_eq!(ints(&take(Array::scalar_i64(6), &short, &mut c)), vec![1, 2, 3, 0, 0, 0]);
15824        // and at the front for a negative one.
15825        assert_eq!(ints(&take(Array::scalar_i64(-6), &short, &mut c)), vec![0, 0, 0, 1, 2, 3]);
15826        // A scalar right argument is treated as a one-item vector.
15827        let r = take(Array::scalar_i64(2), &Array::scalar_i64(5), &mut c);
15828        assert_eq!(r.shape, vec![2]);
15829        assert_eq!(ints(&r), vec![5, 0]);
15830        // Per-axis on a matrix.
15831        let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15832        let r = take(Array::scalar_i64(1), &m, &mut c);
15833        assert_eq!(r.shape, vec![1, 3]);
15834        assert_eq!(ints(&r), vec![1, 2, 3]);
15835        let r = take(Array::scalar_i64(-1), &m, &mut c);
15836        assert_eq!(ints(&r), vec![4, 5, 6]);
15837        let r = take(Array::from_i64(vec![2, 2]), &m, &mut c);
15838        assert_eq!(r.shape, vec![2, 2]);
15839        assert_eq!(ints(&r), vec![1, 2, 4, 5]);
15840        let r = take(Array::from_i64(vec![3, -2]), &m, &mut c);
15841        assert_eq!(r.shape, vec![3, 2]);
15842        assert_eq!(ints(&r), vec![2, 3, 5, 6, 0, 0]);
15843        // Character fills are spaces.
15844        let r = head_v()
15845            .dyad(&Array::scalar_i64(3), &Array::from_chars(vec!['a']), &mut c, sp())
15846            .unwrap();
15847        assert_eq!(r.data, Data::Char(vec!['a', ' ', ' '].into()));
15848        // More counts than the argument has axes: a length error, as both
15849        // references answer. Only a scalar right argument stretches.
15850        let e = head_v()
15851            .dyad(&Array::from_i64(vec![1, 1]), &Array::from_i64(vec![1, 2]), &mut c, sp())
15852            .unwrap_err();
15853        assert_eq!(e.kind, ErrorKind::Length);
15854        let r = head_v()
15855            .dyad(&Array::from_i64(vec![1, 2]), &Array::scalar_i64(5), &mut c, sp())
15856            .unwrap();
15857        assert_eq!(r.shape, vec![1, 2]);
15858        assert_eq!(ints(&r), vec![5, 0]);
15859    }
15860
15861    #[test]
15862    fn drop_from_both_ends_and_beyond() {
15863        ctx!(c);
15864        let v = Array::from_i64(vec![1, 2, 3]);
15865        let drop = |x: Array, y: &Array, c: &mut Ctx<'_>| behead_v().dyad(&x, y, c, sp()).unwrap();
15866        assert_eq!(ints(&drop(Array::scalar_i64(1), &v, &mut c)), vec![2, 3]);
15867        assert_eq!(ints(&drop(Array::scalar_i64(-1), &v, &mut c)), vec![1, 2]);
15868        // Dropping more than there is empties the axis.
15869        let r = drop(Array::scalar_i64(5), &v, &mut c);
15870        assert_eq!(r.shape, vec![0]);
15871        assert!(ints(&r).is_empty());
15872        let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15873        let r = drop(Array::scalar_i64(1), &m, &mut c);
15874        assert_eq!(r.shape, vec![1, 3]);
15875        assert_eq!(ints(&r), vec![4, 5, 6]);
15876        let r = drop(Array::from_i64(vec![0, -1]), &m, &mut c);
15877        assert_eq!(r.shape, vec![2, 2]);
15878        assert_eq!(ints(&r), vec![1, 2, 4, 5]);
15879    }
15880
15881    // ------------------------------------------------------------ framing
15882
15883    #[test]
15884    fn cells_of_unequal_shapes_are_padded_with_fills() {
15885        ctx!(c);
15886        // i."0 ] 1 2 3: cells of length 1, 2 and 3 frame into a 3 by 3 table.
15887        let v = Verb::Rank(b(iota()), [0, 0, 0]);
15888        let r = v.monad(&Array::from_i64(vec![1, 2, 3]), &mut c, sp()).unwrap();
15889        assert_eq!(r.shape, vec![3, 3]);
15890        assert_eq!(ints(&r), vec![0, 0, 0, 0, 1, 0, 0, 1, 2]);
15891    }
15892
15893    #[test]
15894    fn framing_aligns_lower_rank_cells_at_the_trailing_axes() {
15895        let cells = vec![Array::from_i64(vec![1, 2]), mat(2, 2, vec![1, 2, 3, 4])];
15896        let r = assemble(&[2], cells, sp()).unwrap();
15897        assert_eq!(r.shape, vec![2, 2, 2]);
15898        assert_eq!(ints(&r), vec![1, 2, 0, 0, 1, 2, 3, 4]);
15899    }
15900
15901    #[test]
15902    fn framing_promotes_cell_types() {
15903        let cells = vec![Array::from_i64(vec![1]), Array::from_f64(vec![2.5])];
15904        let r = assemble(&[2], cells, sp()).unwrap();
15905        assert_eq!(r.dtype(), DType::F64);
15906        assert_eq!(floats(&r), vec![1.0, 2.5]);
15907        // Characters and numbers cannot share a result.
15908        let cells = vec![Array::from_i64(vec![1]), Array::from_chars(vec!['a'])];
15909        let e = assemble(&[2], cells, sp()).unwrap_err();
15910        assert_eq!(e.kind, ErrorKind::Type);
15911    }
15912
15913    #[test]
15914    fn framing_over_an_empty_frame_yields_an_empty_result() {
15915        let r = assemble(&[0], Vec::new(), sp()).unwrap();
15916        assert_eq!(r.shape, vec![0]);
15917        assert_eq!(r.count(), 0);
15918    }
15919
15920    // ------------------------------------------------------------- trains
15921
15922    #[test]
15923    fn fork_applies_both_tines() {
15924        ctx!(c);
15925        // (+/ % #) is the mean.
15926        let v = Verb::Fork(b(Verb::Reduce(b(plus()))), b(pct()), b(pound()));
15927        let r = v.monad(&Array::from_i64(vec![1, 2, 3, 4]), &mut c, sp()).unwrap();
15928        assert!(close(floats(&r)[0], 2.5));
15929        // Dyadically both tines see both arguments: (x-y) + (x+y) = 2x.
15930        let v = Verb::Fork(b(minus()), b(plus()), b(plus()));
15931        let r = v
15932            .dyad(&Array::from_i64(vec![5]), &Array::from_i64(vec![3]), &mut c, sp())
15933            .unwrap();
15934        assert_eq!(ints(&r), vec![10]);
15935    }
15936
15937    #[test]
15938    fn noun_fork_supplies_a_constant_left_argument() {
15939        ctx!(c);
15940        let v = Verb::NounFork(Array::scalar_i64(10), b(minus()), b(right_v()));
15941        let r = v.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
15942        assert_eq!(ints(&r), vec![9, 8]);
15943        let r = v
15944            .dyad(&Array::scalar_i64(0), &Array::from_i64(vec![1, 2]), &mut c, sp())
15945            .unwrap();
15946        assert_eq!(ints(&r), vec![9, 8]);
15947    }
15948
15949    #[test]
15950    fn hook_reuses_its_right_argument() {
15951        ctx!(c);
15952        // y + (-y) is zero.
15953        let v = Verb::Hook(b(plus()), b(minus()));
15954        let r = v.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
15955        assert_eq!(ints(&r), vec![0, 0]);
15956        // x + (-y)
15957        let r = v
15958            .dyad(&Array::from_i64(vec![10]), &Array::from_i64(vec![3]), &mut c, sp())
15959            .unwrap();
15960        assert_eq!(ints(&r), vec![7]);
15961    }
15962
15963    #[test]
15964    fn atop_composes() {
15965        ctx!(c);
15966        let v = Verb::Atop(b(minus()), b(plus()));
15967        let r = v.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
15968        assert_eq!(ints(&r), vec![-1, -2]);
15969        let r = v
15970            .dyad(&Array::from_i64(vec![1]), &Array::from_i64(vec![2]), &mut c, sp())
15971            .unwrap();
15972        assert_eq!(ints(&r), vec![-3]);
15973    }
15974
15975    #[test]
15976    fn trains_apply_to_the_whole_argument() {
15977        // No train iterates cells of its own.
15978        assert_eq!(Verb::Hook(b(plus()), b(minus())).ranks(), [RANK_INF; 3]);
15979        assert_eq!(Verb::Reduce(b(plus())).ranks(), [RANK_INF; 3]);
15980    }
15981
15982    // ------------------------------------------------------- missing cases
15983
15984    #[test]
15985    fn absent_and_unwritten_meanings_are_reported_differently() {
15986        ctx!(c);
15987        let e = eq_v().monad(&Array::scalar_i64(1), &mut c, sp()).unwrap_err();
15988        assert_eq!(e.kind, ErrorKind::Domain);
15989        assert!(e.msg.contains("no monadic meaning"), "{}", e.msg);
15990        let e = not_v()
15991            .dyad(&Array::scalar_i64(1), &Array::scalar_i64(1), &mut c, sp())
15992            .unwrap_err();
15993        assert_eq!(e.kind, ErrorKind::Domain);
15994        assert!(e.msg.contains("no dyadic meaning"), "{}", e.msg);
15995        let e = pound()
15996            .dyad(&Array::scalar_i64(1), &Array::scalar_i64(1), &mut c, sp())
15997            .unwrap_err();
15998        assert_eq!(e.kind, ErrorKind::NotYet);
15999        assert!(e.msg.contains("copy"), "{}", e.msg);
16000        // Echo's output formatting belongs to fmt; only its result is checked.
16001        let _ = echo_v();
16002    }
16003
16004    // ----------------------------------------------------- parallel paths
16005    //
16006    // Every case here runs the same application twice, on a pool of one
16007    // thread and on a pool of four, and compares the two: the sequential
16008    // result is the contract, and the argument sizes are chosen to be over
16009    // the threshold so the parallel path is really taken.
16010
16011    /// The result of `f` under one thread and under four.
16012    fn seq_par<T: Send>(f: impl Fn() -> T + Sync + Send) -> (T, T) {
16013        (par::with_threads(1, &f), par::with_threads(4, &f))
16014    }
16015
16016    /// A deterministic spread of values, positive and negative.
16017    fn noise(n: usize) -> Vec<f64> {
16018        let mut x = 0x2545_f491_4f6c_dd1du64;
16019        (0..n)
16020            .map(|_| {
16021                x ^= x << 13;
16022                x ^= x >> 7;
16023                x ^= x << 17;
16024                (x >> 11) as f64 / (1u64 << 53) as f64 - 0.5
16025            })
16026            .collect()
16027    }
16028
16029    fn f64_mat(rows: usize, cols: usize) -> Array {
16030        Array::new(vec![rows, cols], Data::F64(noise(rows * cols).into()))
16031    }
16032
16033    /// Above `par::MIN_WORK`, so anything elementwise splits.
16034    const BIG: usize = 200_000;
16035
16036    #[test]
16037    fn an_elementwise_dyad_splits_into_the_same_result() {
16038        let x = Array::from_f64(noise(BIG));
16039        let y = Array::from_f64(noise(BIG).iter().map(|v| v + 0.25).collect());
16040        let (one, many) = seq_par(|| {
16041            ctx!(c);
16042            times().dyad(&x, &y, &mut c, sp()).unwrap()
16043        });
16044        assert_eq!(floats(&one), floats(&many));
16045        // A scalar left argument takes the broadcasting shape of the loop.
16046        let (one, many) = seq_par(|| {
16047            ctx!(c);
16048            plus().dyad(&Array::scalar_f64(0.5), &y, &mut c, sp()).unwrap()
16049        });
16050        assert_eq!(floats(&one), floats(&many));
16051    }
16052
16053    #[test]
16054    fn an_elementwise_dyad_that_overflows_widens_the_same_way() {
16055        // One pair overflows i64, so the whole pass is redone in floats
16056        // however the chunks fell.
16057        let mut v = vec![1i64; BIG];
16058        v[BIG - 3] = i64::MAX;
16059        let x = Array::from_i64(v);
16060        let (one, many) = seq_par(|| {
16061            ctx!(c);
16062            plus().dyad(&x, &x, &mut c, sp()).unwrap()
16063        });
16064        assert_eq!(one.dtype(), DType::F64);
16065        assert_eq!(floats(&one), floats(&many));
16066    }
16067
16068    #[test]
16069    fn an_elementwise_monad_splits_into_the_same_result() {
16070        let y = Array::from_f64(noise(BIG));
16071        for v in [minus(), sqrt_v(), floor_v(), pct()] {
16072            let (one, many) = seq_par(|| {
16073                ctx!(c);
16074                v.monad(&Array::from_f64(y.as_f64_slice().unwrap().iter().map(|x| x.abs()).collect()), &mut c, sp())
16075                    .unwrap()
16076            });
16077            assert_eq!(one.data, many.data, "{}", v.name());
16078        }
16079    }
16080
16081    #[test]
16082    fn monadic_cells_run_in_parallel_and_frame_in_order() {
16083        // 400 cells of 512 elements: over the threshold, and every cell
16084        // yields a different value, so a misplaced cell would show.
16085        let y = f64_mat(400, 512);
16086        let v = Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]);
16087        let (one, many) = seq_par(|| {
16088            ctx!(c);
16089            v.monad(&y, &mut c, sp()).unwrap()
16090        });
16091        assert_eq!(one.shape, vec![400]);
16092        assert_eq!(floats(&one), floats(&many));
16093    }
16094
16095    #[test]
16096    fn dyadic_cells_run_in_parallel_and_frame_in_order() {
16097        let x = f64_mat(400, 512);
16098        let y = f64_mat(400, 512);
16099        // Rank 1: the frame is the rows, and each row pair is one cell.
16100        let v = Verb::Rank(b(plus()), [1, 1, 1]);
16101        let (one, many) = seq_par(|| {
16102            ctx!(c);
16103            v.dyad(&x, &y, &mut c, sp()).unwrap()
16104        });
16105        assert_eq!(one.shape, vec![400, 512]);
16106        assert_eq!(floats(&one), floats(&many));
16107    }
16108
16109    #[test]
16110    fn a_verb_that_writes_output_is_not_pure() {
16111        assert!(plus().is_pure());
16112        assert!(Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]).is_pure());
16113        assert!(!echo_v().is_pure());
16114        assert!(!Verb::Rank(b(Verb::Atop(b(echo_v()), b(plus()))), [1, 1, 1]).is_pure());
16115    }
16116
16117    #[test]
16118    fn an_impure_verb_keeps_its_cells_in_order() {
16119        // Enough elements to pass the threshold; the cells must still be
16120        // written one after another, in index order.
16121        let y = Array::new(vec![16, 8192], Data::I64((0..16 * 8192).collect::<Vec<i64>>().into()));
16122        let v = Verb::Rank(b(Verb::Atop(b(echo_v()), b(head_v()))), [1, 1, 1]);
16123        let mut seen: Vec<i64> = Vec::new();
16124        let mut sink = |s: &str| {
16125            if let Some(first) = s.split_whitespace().next() && let Ok(n) = first.parse::<i64>() {
16126                seen.push(n);
16127            }
16128        };
16129        let mut env = Env::new(Vec::new());
16130        let mut c = Ctx {
16131            cfg: EvalCfg {
16132                agreement: Agreement::LeadingPrefix,
16133                fmt: FmtOpts::J,
16134                tol: Tol::J,
16135                rules: Rules::default(),
16136            },
16137            out: &mut sink,
16138            inp: None,
16139            env: &mut env,
16140            device: None,
16141        };
16142        v.monad(&y, &mut c, sp()).unwrap();
16143        assert_eq!(seen, (0..16).map(|i| i * 8192).collect::<Vec<i64>>());
16144    }
16145
16146    #[test]
16147    fn a_wide_item_reduce_folds_every_column_in_order() {
16148        // item_size over par::WIDE_ITEM: each output element folds its own
16149        // column, so even a non-associative fold matches exactly.
16150        let y = f64_mat(300, 512);
16151        for v in [plus(), minus(), floor_v()] {
16152            let (one, many) = seq_par(|| {
16153                ctx!(c);
16154                Verb::Reduce(b(v.clone())).monad(&y, &mut c, sp()).unwrap()
16155            });
16156            assert_eq!(one.shape, vec![512]);
16157            assert_eq!(floats(&one), floats(&many), "{}", v.name());
16158        }
16159    }
16160
16161    #[test]
16162    fn a_wide_item_integer_reduce_is_exact() {
16163        let n = 300;
16164        let m = 512;
16165        let y = Array::new(
16166            vec![n, m],
16167            Data::I64((0..(n * m) as i64).map(|i| i % 977 - 400).collect::<Vec<i64>>().into()),
16168        );
16169        let (one, many) = seq_par(|| {
16170            ctx!(c);
16171            Verb::Reduce(b(minus())).monad(&y, &mut c, sp()).unwrap()
16172        });
16173        assert_eq!(ints(&one), ints(&many));
16174    }
16175
16176    #[test]
16177    fn a_narrow_item_reduce_chunks_the_items() {
16178        // item_size under par::WIDE_ITEM and an associative verb: the items
16179        // are chunked, which reassociates a float sum (§5.9) but not an
16180        // integer one.
16181        let y = f64_mat(300_000, 8);
16182        let (one, many) = seq_par(|| {
16183            ctx!(c);
16184            Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
16185        });
16186        assert_eq!(one.shape, vec![8]);
16187        for (p, q) in floats(&one).iter().zip(floats(&many)) {
16188            assert!((p - q).abs() <= 1e-12 * p.abs().max(1.0), "{p} vs {q}");
16189        }
16190        let ints_y = Array::new(
16191            vec![300_000, 8],
16192            Data::I64((0..300_000 * 8).map(|i| (i % 101) as i64 - 50).collect::<Vec<i64>>().into()),
16193        );
16194        let (one, many) = seq_par(|| {
16195            ctx!(c);
16196            Verb::Reduce(b(plus())).monad(&ints_y, &mut c, sp()).unwrap()
16197        });
16198        assert_eq!(ints(&one), ints(&many));
16199    }
16200
16201    #[test]
16202    fn a_vector_reduce_folds_the_flat_buffer() {
16203        let y = Array::from_f64(noise(BIG * 4));
16204        let (one, many) = seq_par(|| {
16205            ctx!(c);
16206            Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
16207        });
16208        let (p, q) = (floats(&one)[0], floats(&many)[0]);
16209        assert!((p - q).abs() <= 1e-12 * p.abs().max(1.0), "{p} vs {q}");
16210
16211        // Integers are exact, and a non-associative fold is not regrouped
16212        // at all, so it matches to the bit.
16213        let ints_y = Array::from_i64((0..BIG as i64 * 4).map(|i| i % 1009 - 500).collect());
16214        for v in [plus(), minus(), ceil_v()] {
16215            let (one, many) = seq_par(|| {
16216                ctx!(c);
16217                Verb::Reduce(b(v.clone())).monad(&ints_y, &mut c, sp()).unwrap()
16218            });
16219            assert_eq!(ints(&one), ints(&many), "{}", v.name());
16220        }
16221    }
16222
16223    #[test]
16224    fn a_reduce_that_overflows_falls_back_to_the_sequential_widening() {
16225        let mut v: Vec<i64> = vec![1; BIG];
16226        v[7] = i64::MAX;
16227        let y = Array::from_i64(v);
16228        let (one, many) = seq_par(|| {
16229            ctx!(c);
16230            Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
16231        });
16232        assert_eq!(one.dtype(), DType::F64);
16233        assert_eq!(floats(&one), floats(&many));
16234    }
16235
16236    #[test]
16237    fn a_boolean_reduce_matches_the_sequential_promotion() {
16238        let n = BIG;
16239        let y = Array::new(
16240            vec![n],
16241            Data::Bool((0..n).map(|i| (i % 3 == 0) as u8).collect::<Vec<u8>>().into()),
16242        );
16243        let (one, many) = seq_par(|| {
16244            ctx!(c);
16245            Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
16246        });
16247        assert_eq!(one.dtype(), DType::I64);
16248        assert_eq!(ints(&one), ints(&many));
16249        assert_eq!(ints(&one)[0], n.div_ceil(3) as i64);
16250    }
16251}