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::{
17    ComplexOrder, EncodeDigits, FloorRule, InnerEach, NearCount, NestedGrade, Rules,
18};
19use crate::par;
20use crate::simd::multiversioned;
21
22/// Infinite rank (applies to the argument as a whole).
23pub const RANK_INF: i64 = i64::MAX;
24
25/// How dyadic frames must agree. A property of the source language,
26/// fixed per compiled program.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum Agreement {
29    /// J: the shorter frame must be a prefix of the longer.
30    LeadingPrefix,
31    /// APL scalar conformability: equal frames, or one of them empty.
32    ExactOrScalar,
33}
34
35/// How close two floats have to be to count as equal.
36///
37/// Both languages compare reals with a relative tolerance: J's `9!:18`
38/// comparison tolerance, APL's `⎕CT`. Two values are equal when they differ
39/// by less than the tolerance scaled by one of their magnitudes — the
40/// smaller one in J, the larger one in APL. Both references answer strictly:
41/// a difference exactly at the threshold is not equal. Integers, characters
42/// and boxes are unaffected, and an exact bit-for-bit equality (the
43/// infinities included) is equality whatever the tolerance is.
44#[derive(Clone, Copy, Debug, PartialEq)]
45pub struct Tol {
46    /// Relative tolerance; zero compares exactly.
47    pub ct: f64,
48    /// Scale by the smaller magnitude (J) rather than the larger (APL).
49    pub by_smaller: bool,
50    /// Which reading `⌊` and `⌈` take. Unread under J, whose floor is
51    /// the tolerant comparison itself.
52    pub floor_rule: FloorRule,
53}
54
55impl Tol {
56    /// No tolerance at all — J's `u!.0`.
57    pub const EXACT: Tol = Tol { ct: 0.0, by_smaller: true, floor_rule: FloorRule::Shift };
58    /// J's default comparison tolerance, 2^-44.
59    pub const J: Tol =
60        Tol { ct: 5.684_341_886_080_802e-14, by_smaller: true, floor_rule: FloorRule::Shift };
61    /// GNU APL's default `⎕CT`.
62    pub const APL: Tol = Tol { ct: 1e-13, by_smaller: false, floor_rule: FloorRule::Shift };
63
64    /// Tolerant equality.
65    #[inline(always)]
66    pub fn eq(self, a: f64, b: f64) -> bool {
67        if a == b {
68            return true;
69        }
70        // NaN and unequal infinities fail every comparison below, which is
71        // what both references answer for them.
72        let s = if self.by_smaller {
73            a.abs().min(b.abs())
74        } else {
75            a.abs().max(b.abs())
76        };
77        (a - b).abs() < self.ct * s
78    }
79
80    /// Whose rule this is. A scalar verb is handed the tolerance and
81    /// nothing else about the dialect, and two rules below need to know
82    /// which one they are under: J reads a magnitude below the tolerance
83    /// as zero, and J's equality is total across the box boundary where
84    /// APL's reaches inside the box instead.
85    #[inline(always)]
86    pub fn is_j(self) -> bool {
87        self.by_smaller
88    }
89
90    /// Whether the tolerance reads this magnitude as zero.
91    ///
92    /// J's signum does: `* 1e_15` is 0 and `* 6e_14` is 1, the threshold
93    /// being the tolerance itself. APL's `×` is exact there. With `!.0` the
94    /// tolerance is zero, so the rule falls away with it.
95    #[inline(always)]
96    pub fn is_zero(self, y: f64) -> bool {
97        self.is_j() && y.abs() < self.ct
98    }
99
100    /// Tolerant `<`: less, and not tolerantly equal.
101    #[inline(always)]
102    pub fn lt(self, a: f64, b: f64) -> bool {
103        a < b && !self.eq(a, b)
104    }
105
106    /// Tolerant `<=`: less, or tolerantly equal.
107    #[inline(always)]
108    pub fn le(self, a: f64, b: f64) -> bool {
109        a <= b || self.eq(a, b)
110    }
111
112    /// Tolerant equality on complex values: the magnitude of the difference
113    /// against the same scale the real comparison uses. J answers
114    /// `3j4 = 3.0000000000001j4` with 1, which is this rule on magnitudes.
115    #[inline]
116    pub fn eq_cx(self, a: Cx, b: Cx) -> bool {
117        if a == b {
118            return true;
119        }
120        let (ma, mb) = (cx::abs(a), cx::abs(b));
121        let s = if self.by_smaller { ma.min(mb) } else { ma.max(mb) };
122        cx::abs(cx::sub(a, b)) < self.ct * s
123    }
124
125    /// `<. y`: the largest integer not above y, with a value just under an
126    /// integer counting as that integer.
127    ///
128    /// The three readings were each probed. J scales the gap by the
129    /// magnitude, so `<. 99.999999999995` is 100 and `<. _1e_14` is `_1`.
130    /// GNU APL shifts by the tolerance itself, so `⌊99.999999999995` is 99
131    /// — the gap of 5e¯12 is larger than `⎕CT` however big the value is —
132    /// while `⌊¯1E¯13` is 0. Dyalog scales the shift by the magnitude but
133    /// never below 1, which keeps `⌊¯1E¯14` at 0 and lifts
134    /// `⌊9.9999999999999` to 10.
135    #[inline(always)]
136    pub fn floor(self, y: f64) -> f64 {
137        if self.is_j() {
138            let c = y.ceil();
139            if self.eq(y, c) { c } else { y.floor() }
140        } else if self.floor_rule == FloorRule::Shift {
141            (y + self.ct).floor()
142        } else {
143            // The gap is compared against the step rather than added to
144            // the value: `999.99999999999 + 9.9999999999999E¯12` rounds up
145            // to a clean 1000 in double arithmetic where the exact sum is
146            // still below it, and Dyalog answers 999.
147            let c = y.ceil();
148            if c - y <= self.ct * y.abs().max(1.0) { c } else { y.floor() }
149        }
150    }
151
152    /// `>. y`: the ceiling, with a value just over an integer counting as
153    /// that integer. The three readings are [`Tol::floor`]'s, mirrored.
154    #[inline(always)]
155    pub fn ceil(self, y: f64) -> f64 {
156        if self.is_j() {
157            let f = y.floor();
158            if self.eq(y, f) { f } else { y.ceil() }
159        } else if self.floor_rule == FloorRule::Shift {
160            (y - self.ct).ceil()
161        } else {
162            let f = y.floor();
163            if y - f <= self.ct * y.abs().max(1.0) { f } else { y.ceil() }
164        }
165    }
166
167    /// `x | y`: the remainder of y on division by x, with the quotient read
168    /// tolerantly. Both references round the quotient before subtracting,
169    /// which is what makes `0.1|0.3` zero rather than a rounding error, and
170    /// each rounds it its own way.
171    ///
172    /// J takes the tolerant floor of the quotient and then answers an exact
173    /// zero whenever the product is tolerantly the dividend: `2 | 1e_14` is
174    /// `1e_14` (the quotient is nowhere near an integer) while
175    /// `2 | 4 + 1e_14` is 0 (the product 4 is tolerantly the dividend).
176    ///
177    /// GNU APL reads the remainder against the MODULUS instead: a remainder
178    /// within `⎕CT` of the modulus's magnitude is zero, so `2|1E¯14` is 0
179    /// where J keeps the `1e_14`. A remainder that rounding has pushed out
180    /// of `[0, x)` comes back into range.
181    #[inline]
182    pub fn residue(self, x: f64, y: f64) -> f64 {
183        // An infinite DIVIDEND has no residue at all under any nonzero
184        // modulus: jconsole refuses `2 | _`, `0.5 | _`, `_1 | _` and `_ | _`
185        // alike with a NaN error, and the NaN made here is what
186        // [`Tol::made_nan`] turns into that refusal. A zero modulus is the
187        // exception, because it never divides: `0 | _` is `_`.
188        if self.is_j() && y.is_infinite() && x != 0.0 {
189            return f64::NAN;
190        }
191        // An infinite modulus leaves a value of its own sign alone and
192        // sends the other one to that infinity, which is the limit both
193        // references answer with; the general formula cannot reach it,
194        // because it runs into `inf * 0`.
195        if x.is_infinite() {
196            return if y == 0.0 || (y > 0.0) == (x > 0.0) { y } else { x };
197        }
198        if x == 0.0 {
199            return y;
200        }
201        if self.is_j() {
202            let p = x * self.floor(y / x);
203            return if self.eq(y, p) { 0.0 } else { y - p };
204        }
205        // GNU APL counts the quotient as its ceiling when the gap to it is
206        // within `⎕CT` either outright or relative to the magnitude: the
207        // first is what makes `1|¯1E¯14` zero, the second what makes
208        // `1E¯15|1` zero, where the quotient is 1e15 and the gap 0.1.
209        let q = y / x;
210        let c = q.ceil();
211        let gap = c - q;
212        let k = if gap <= self.ct || gap < self.ct * q.abs().max(c.abs()) { c } else { q.floor() };
213        let r = y - x * k;
214        if r.abs() < self.ct * x.abs() {
215            0.0
216        } else if r != 0.0 && (r < 0.0) != (x < 0.0) {
217            r + x
218        } else {
219            r
220        }
221    }
222
223    /// `x * y`, with J's rule that a zero factor wins.
224    ///
225    /// J defines `0 * _` as 0 where IEEE arithmetic has no value for it, and
226    /// the rule is the factor's, not the product's: `0 * _.` is 0 too, and
227    /// `*/ 0 , _` is 0. It is also what gives `j. _` its value, because a
228    /// complex product is four real ones and `_ * 0j1` is `0j_` only when
229    /// each of them follows this rule. APL never meets the case — GNU APL
230    /// refuses an infinite operand to `×` outright — so the rule is J's
231    /// alone and a finite pair is untouched, negative zero included.
232    #[inline(always)]
233    pub fn mul(self, x: f64, y: f64) -> f64 {
234        if self.is_j() && (x == 0.0 || y == 0.0) && !(x.is_finite() && y.is_finite()) {
235            return 0.0;
236        }
237        x * y
238    }
239
240    /// Whether a result must be refused because the arithmetic MADE this
241    /// NaN: J answers `_ - _`, `_ % _`, `2 | _`, `0 ^. 0` and `! __` with a
242    /// NaN error, while a NaN the program itself wrote travels on unrefused
243    /// (`_. + 1` is `_.`). Distinguishing the two is exactly the operand
244    /// test below. APL never reaches a NaN with a value of its own, so the
245    /// rule stays J's.
246    #[inline(always)]
247    pub fn made_nan(self, r: f64, x: f64, y: f64) -> bool {
248        self.is_j() && r.is_nan() && !x.is_nan() && !y.is_nan()
249    }
250}
251
252/// One infinity or NaN in J's own spelling, for a diagnostic that has the
253/// value and not the text the user wrote.
254pub(crate) fn j_number(v: f64) -> String {
255    if v.is_nan() {
256        "_.".to_string()
257    } else if v == f64::INFINITY {
258        "_".to_string()
259    } else if v == f64::NEG_INFINITY {
260        "__".to_string()
261    } else {
262        format!("{v}")
263    }
264}
265
266/// The effect-free half of the execution context. Copyable, so a path that
267/// runs cells on other threads can carry it there; neither the output sink
268/// nor the input source can go along, which is what keeps those paths pure
269/// by construction.
270#[derive(Clone, Copy, Debug)]
271pub struct EvalCfg {
272    pub agreement: Agreement,
273    pub fmt: FmtOpts,
274    /// Comparison tolerance in force; it starts as the dialect's and `u!.n`
275    /// overrides it inside the verb it is attached to.
276    pub tol: Tol,
277    /// The dialect's settings, resolved once at compile time. A rule that
278    /// only bites at run time reads it from here rather than deducing it.
279    pub rules: Rules,
280}
281
282impl EvalCfg {
283    /// Run `f` with a context whose sink is never reached, and whose names
284    /// are empty. Only a verb that [`Verb::is_pure`] accepted is given one
285    /// of these, and an explicit definition — the only thing that reads
286    /// names — is never pure.
287    /// The near-integer admission counts, lengths and indices are read
288    /// with here. In J and in GNU APL it is the language's and no setting
289    /// moves it; Dyalog's follows `⎕CT`, so the dialect names which.
290    pub(crate) fn near(self) -> NearInt {
291        match self.rules.lang {
292            crate::Lang::J => NearInt::J,
293            crate::Lang::Apl => match self.rules.near_count {
294                NearCount::Absolute => NearInt::Apl,
295                NearCount::Tolerant => NearInt::Tolerant(self.rules.tol()),
296            },
297        }
298    }
299
300    pub(crate) fn pure<R>(self, f: impl FnOnce(&mut Ctx<'_>) -> R) -> R {
301        let mut sink = |_: &str| debug_assert!(false, "a pure verb wrote to the output sink");
302        let mut env = Env::new(Vec::new());
303        f(&mut Ctx { cfg: self, out: &mut sink, inp: None, env: &mut env, device: None, shy: false })
304    }
305}
306
307/// How deep explicit definitions may call each other before libjay stops
308/// them. Recursion that runs away is a program bug; the diagnostic says so
309/// rather than letting the process die on a stack overflow.
310///
311/// The number is set by the machine stack, not by the languages: one level
312/// of a definition costs about 24 kB of stack in an unoptimised build, so
313/// the guard has to fire well inside the 2 MiB a small thread gets. It can
314/// rise when the evaluator's frames shrink.
315pub const RECURSION_LIMIT: usize = 64;
316
317/// The names a running program can reach: the values it has assigned, the
318/// verbs it has named, and the arguments bound to its parameters.
319///
320/// An explicit definition runs with a frame of its own on top: J's `=.`
321/// writes there and `=:` writes to the globals, and a name is looked for in
322/// the frame before the globals. Frames do not nest — a definition called
323/// from another sees only its own locals, which is what both references do.
324pub struct Env {
325    globals: HashMap<String, Array>,
326    frames: Vec<HashMap<String, Array>>,
327    /// The definitions currently running, innermost last; J's `$:` and
328    /// APL's `∇` name the last of them.
329    running: Vec<std::sync::Arc<crate::ir::ExplicitDef>>,
330    verbs: HashMap<String, Verb>,
331    args: Vec<Array>,
332}
333
334impl Env {
335    pub fn new(args: Vec<Array>) -> Env {
336        Env {
337            globals: HashMap::new(),
338            frames: Vec::new(),
339            running: Vec::new(),
340            verbs: HashMap::new(),
341            args,
342        }
343    }
344
345    pub fn get(&self, name: &str) -> Option<Array> {
346        if let Some(frame) = self.frames.last() && let Some(v) = frame.get(name) {
347            return Some(v.clone());
348        }
349        // A dfn written inside another reads the names the enclosing one
350        // made local: `{a←10 ⋄ {a+⍵} ⍵} 5` is 15. Only a LEXICAL parent
351        // counts, so an unrelated caller's locals stay its own — the
352        // frames below are searched, and only those whose definition this
353        // one is written inside are read.
354        if let Some(def) = self.running.last()
355            && !def.enclosing.is_empty()
356        {
357            for i in (0..self.frames.len().saturating_sub(1)).rev() {
358                if def.enclosing.contains(&self.running[i].id)
359                    && let Some(v) = self.frames[i].get(name)
360                {
361                    return Some(v.clone());
362                }
363            }
364        }
365        self.globals.get(name).cloned()
366    }
367
368    pub fn assign(&mut self, name: String, value: Array, scope: crate::ir::Scope) {
369        if scope == crate::ir::Scope::LocalDefault && self.get(&name).is_some() {
370            return;
371        }
372        let target = match (scope, self.frames.last_mut()) {
373            (crate::ir::Scope::Local | crate::ir::Scope::LocalDefault, Some(frame)) => frame,
374            _ => &mut self.globals,
375        };
376        target.insert(name, value);
377    }
378
379    pub fn define(&mut self, name: String, verb: Verb) {
380        self.verbs.insert(name, verb);
381    }
382
383    /// A global by name, reached past any frame. An operator's array
384    /// operand lives here for as long as its body runs, so that the body's
385    /// own frame does not hide it.
386    pub fn global(&self, name: &str) -> Option<Array> {
387        self.globals.get(name).cloned()
388    }
389
390    pub fn set_global(&mut self, name: String, value: Array) {
391        self.globals.insert(name, value);
392    }
393
394    pub fn unset_global(&mut self, name: &str) {
395        self.globals.remove(name);
396    }
397
398    pub fn undefine(&mut self, name: &str) {
399        self.verbs.remove(name);
400    }
401
402    pub fn verb(&self, name: &str) -> Option<&Verb> {
403        self.verbs.get(name)
404    }
405
406    pub fn arg(&self, i: usize) -> Result<Array> {
407        self.args
408            .get(i)
409            .cloned()
410            .ok_or_else(|| Error::internal("a parameter was read where none is bound"))
411    }
412
413    /// Start a definition's frame. Fails rather than overflowing the stack.
414    pub fn enter(
415        &mut self,
416        frame: HashMap<String, Array>,
417        def: std::sync::Arc<crate::ir::ExplicitDef>,
418        span: Span,
419    ) -> Result<()> {
420        if self.frames.len() >= RECURSION_LIMIT {
421            return Err(Error::new(
422                ErrorKind::Domain,
423                format!("explicit definitions called each other more than {RECURSION_LIMIT} deep"),
424                Some(span),
425            )
426            .note("a definition that recurses needs a case that stops"));
427        }
428        self.frames.push(frame);
429        self.running.push(def);
430        Ok(())
431    }
432
433    /// End a definition's frame and hand back the names it assigned.
434    pub fn leave(&mut self) -> HashMap<String, Array> {
435        self.running.pop();
436        self.frames.pop().unwrap_or_default()
437    }
438
439    /// The innermost definition now running; `$:` and `∇` name it.
440    pub fn current_def(&self) -> Option<std::sync::Arc<crate::ir::ExplicitDef>> {
441        self.running.last().cloned()
442    }
443}
444
445/// A run's source of input: one line per call, with no line terminator,
446/// and `None` once the input has ended.
447///
448/// `None` in place of the closure is a run the host attached no input to at
449/// all, which is a different thing from a source that has run out: the
450/// first is a wiring mistake in the embedding, the second is the program
451/// asking for more than it was given, and the two say so differently.
452pub type InputFn<'a> = Option<&'a mut dyn FnMut() -> Option<String>>;
453
454/// Lend an input source to a shorter-lived context. A `&mut` inside an
455/// `Option` does not reborrow on its own, so the borrow is taken apart and
456/// put back.
457pub fn reborrow_input<'s, 'a: 's>(inp: &'s mut InputFn<'a>) -> InputFn<'s> {
458    match inp {
459        Some(f) => Some(&mut **f),
460        None => None,
461    }
462}
463
464/// Execution context threaded through evaluation.
465pub struct Ctx<'a> {
466    pub cfg: EvalCfg,
467    /// Sink for explicit output (`echo`, `⎕←`, `⍞←`). stdout by default per
468    /// the sandbox contract; the host may redirect.
469    pub out: &'a mut dyn FnMut(&str),
470    /// Source for explicit input (`⍞`, `⎕`, J's `1!:1 ]1`). stdin by
471    /// default per the sandbox contract; the host may redirect, and a host
472    /// that attaches none makes every read a diagnostic.
473    pub inp: InputFn<'a>,
474    /// The names the program has bound so far.
475    pub env: &'a mut Env,
476    /// Where the run was placed. None is the CPU, which is also what every
477    /// path that cannot use a device does; only a fused node reads it.
478    pub device: Option<&'a crate::device::Device>,
479    /// Whether the value most recently produced by a SENTENCE is shy: a
480    /// value that flows to whatever consumes it and that a session does
481    /// not display. An APL definition whose answer came from an assignment
482    /// has one, and so does `⎕←`, which has displayed it already. Every
483    /// application clears it on the way in, an explicit definition sets it
484    /// on the way out, and [`crate::ir::Program::run_detail`] reads it.
485    pub shy: bool,
486}
487
488/// How deep one application may sit inside another before libjay stops.
489///
490/// Every level costs stack frames — in the expression walk, in the rank
491/// machinery, in a verb's own tree — and a string is the interface, so a
492/// pathological one must come back as a diagnostic rather than take the
493/// host process down with it. The count is per THREAD, which is what a
494/// stack belongs to: a cell handed to another worker starts from zero on a
495/// stack of its own.
496const MAX_NESTING: usize = 400;
497
498thread_local! {
499    static NESTING: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
500}
501
502/// Report a tree already known to be too deep to walk.
503pub(crate) fn check_nesting(depth: usize, span: Span) -> Result<()> {
504    if depth > MAX_NESTING {
505        return Err(Error::new(
506            ErrorKind::Limit,
507            format!("this program nests more than {MAX_NESTING} applications deep"),
508            Some(span),
509        ));
510    }
511    Ok(())
512}
513
514/// One level of nesting, released when it goes out of scope.
515pub(crate) struct Nesting;
516
517impl Nesting {
518    /// Claim a level, or report that the program nests too deeply.
519    pub(crate) fn enter(span: Span) -> Result<Nesting> {
520        let depth = NESTING.with(|c| {
521            let d = c.get() + 1;
522            c.set(d);
523            d
524        });
525        if depth > MAX_NESTING {
526            NESTING.with(|c| c.set(c.get() - 1));
527            return Err(Error::new(
528                ErrorKind::Limit,
529                format!("this program nests more than {MAX_NESTING} applications deep"),
530                Some(span),
531            ));
532        }
533        Ok(Nesting)
534    }
535}
536
537impl Drop for Nesting {
538    fn drop(&mut self) {
539        NESTING.with(|c| c.set(c.get().saturating_sub(1)));
540    }
541}
542
543impl Ctx<'_> {
544    /// Run `f` in this context with the comparison tolerance replaced.
545    fn with_tol<R>(&mut self, tol: Tol, f: impl FnOnce(&mut Ctx<'_>) -> R) -> R {
546        let cfg = EvalCfg { tol, ..self.cfg };
547        f(&mut Ctx {
548            cfg,
549            out: &mut *self.out,
550            inp: reborrow_input(&mut self.inp),
551            env: &mut *self.env,
552            device: self.device,
553            shy: self.shy,
554        })
555    }
556
557    /// One line of input, without its terminator.
558    ///
559    /// Both ways of having no line are errors rather than empty strings: a
560    /// program that asks for input reaches for something the host has to
561    /// have supplied, and an empty line is a line.
562    pub(crate) fn read_line(&mut self, span: Span) -> Result<String> {
563        let Some(read) = self.inp.as_deref_mut() else {
564            return Err(Error::new(
565                ErrorKind::Value,
566                "this expression reads input, and this run has no input source attached",
567                Some(span),
568            )
569            .note("attach one with Program::run_io (Rust), input= (Python), or jay_run_io (C)"));
570        };
571        read().ok_or_else(|| {
572            Error::new(ErrorKind::Value, "the input has ended: there is no line to read", Some(span))
573        })
574    }
575}
576
577/// Elementwise monadic operations (cell rank 0).
578#[derive(Clone, Copy, Debug, PartialEq, Eq)]
579pub enum ScalarMonad {
580    /// Identity on reals (J `+`, APL `+`).
581    Conj,
582    Neg,
583    Signum,
584    Recip,
585    Sqrt,
586    Exp,
587    Abs,
588    Floor,
589    Ceil,
590    /// APL `~`: logical negation; the argument must be 0 or 1.
591    Not,
592    /// J `-.`: `1 - y` on any number (a superset of logical negation).
593    OneMinus,
594    /// `y + 1` (J `>:`).
595    Inc,
596    /// `y - 1` (J `<:`).
597    Dec,
598    /// `y + y` (J `+:`).
599    Double,
600    /// `y % 2` (J `-:`); always float.
601    Halve,
602    /// `y * y` (J `*:`).
603    Square,
604    /// Natural logarithm (J `^.`, APL `⍟`); always float.
605    Ln,
606    /// `pi * y` (J/APL monadic `o.` / `○`); always float.
607    Pi,
608    /// `! y`: factorial, i.e. the gamma function at y+1. Always float, as in
609    /// J; a negative integer is a pole and yields a signed infinity.
610    Factorial,
611    /// J `j. y`: `0j1 * y`. Always complex.
612    Imaginary,
613    /// J `r. y`: `^ 0j1 * y`, the unit complex at angle y. Always complex.
614    Polar,
615}
616
617/// Elementwise dyadic operations (cell ranks 0 0).
618#[derive(Clone, Copy, Debug, PartialEq, Eq)]
619pub enum ScalarDyad {
620    Add,
621    Sub,
622    Mul,
623    /// J `%`: result is float; `0 % 0` is 0, `n % 0` is signed infinity.
624    DivJ,
625    /// APL `÷`: result is float; `0 ÷ 0` is 1, `n ÷ 0` is a domain error.
626    DivApl,
627    Min,
628    Max,
629    Pow,
630    /// `x | y`: y modulo x, sign following x; `0 | y` is y.
631    Residue,
632    Eq,
633    Ne,
634    Lt,
635    Le,
636    Gt,
637    Ge,
638    /// Least common multiple (J `*.`, APL `∧`); logical and on booleans.
639    Lcm,
640    /// Greatest common divisor (J `+.`, APL `∨`); logical or on booleans.
641    Gcd,
642    /// `x ^. y` / `x ⍟ y`: logarithm of y to base x; always float.
643    Log,
644    /// `x %: y`: the x-th root of y; always float.
645    Root,
646    /// `k o. y` / `k ○ y`: the circle function selected by the integer k —
647    /// the trigonometric, hyperbolic and inverse families, plus the two
648    /// Pythagorean forms at 0 and 4. Always float.
649    Circle,
650    /// `x ! y`: the number of ways to choose x things from y — J's argument
651    /// order. Defined for every real pair through the gamma function.
652    Binomial,
653    /// J `x j. y`: `x + 0j1 * y`. Always complex.
654    MakeComplex,
655    /// J `x r. y`: `x * ^ 0j1 * y`, i.e. polar coordinates. Always complex.
656    PolarBy,
657}
658
659/// How a value is put into a box.
660#[derive(Clone, Copy, Debug, PartialEq, Eq)]
661pub enum Enclose {
662    /// J `<`: every value becomes a box.
663    Always,
664    /// APL `⊂`: a simple scalar is its own enclosure, so `⊂5` is `5`.
665    ExceptSimpleScalar,
666}
667
668/// Monadic meaning of a primitive.
669#[derive(Clone, Copy, Debug, PartialEq, Eq)]
670pub enum MonadOp {
671    Scalar(ScalarMonad),
672    /// Shape as an integer vector (J `$`, APL `⍴`).
673    ShapeOf,
674    /// Item count as a scalar (J `#`, APL `≢`).
675    Tally,
676    /// All elements as a vector (J/APL `,`).
677    Ravel,
678    /// Each item raveled into a row of a table (J `,.`). The answer never
679    /// has a rank below two, so an atom becomes a one-by-one table.
680    RavelItems,
681    /// Reverse the axes (J `|:`, APL `⍉`).
682    TransposeAxes,
683    /// `{ y`: catalogue — one element from each item of y, in every
684    /// combination, each combination boxed.
685    Catalogue,
686    /// J `5!:1`: the atomic representation of the entity a boxed name
687    /// stands for, boxed. A noun stands for itself, so its representation
688    /// is the pair `('0'; <value)`.
689    AtomicRep,
690    /// `e. y`: raze-in — for every element of y, which items of the raze
691    /// of y it holds.
692    RazeIn,
693    /// First item (J `{.`).
694    Head,
695    /// All but the first item (J `}.`).
696    Behead,
697    /// Last item (J `{:`); a cell of fills when there are no items.
698    Tail,
699    /// All but the last item (J `}:`).
700    Curtail,
701    /// Reverse the items, i.e. along the leading axis (J `|.`, APL `⊖`).
702    Reverse,
703    /// Distinct items in first-occurrence order (J `~.`, APL `∪`).
704    Nub,
705    /// The stable permutation that sorts the items ascending (J `/:`, APL `⍋`).
706    GradeUp { origin: i64 },
707    /// The stable permutation that sorts the items descending (J `\:`, APL `⍒`).
708    GradeDown { origin: i64 },
709    /// J `i.`: integers 0.. filling shape |y|, reversed along negative axes.
710    IotaJ,
711    /// APL `⍳` on a scalar: origin .. origin+y-1.
712    IotaApl { origin: i64 },
713    /// Print the formatted argument, yield an empty array (J `echo`).
714    Echo,
715    /// J `1!:1 y`: one line from the input source as a character vector,
716    /// the terminator dropped. `y` names the stream: 1 is stdin, which the
717    /// sandbox opens, and everything else is a file, which it does not.
718    ReadStream,
719    /// J `3!:0 y`: the code J gives the argument's element type.
720    TypeCode,
721    /// The argument itself (APL `⊢`).
722    Same,
723    /// J `":` / APL `⍕`: the argument as the characters that display it.
724    /// A rank-0 argument gives a character vector, a rank-r one a character
725    /// array of rank r (the display's lines, padded to one width).
726    Format,
727    /// J `#.` / APL monadic base-2 decode: a vector of digits as one number.
728    DecodeBits,
729    /// J `#:`: base-2 encode. The width comes from the largest magnitude in
730    /// the whole argument, so the verb has infinite rank; the digits become
731    /// a new trailing axis.
732    EncodeBits,
733    /// J `,:`: a leading axis of one (shape `2 3` becomes `1 2 3`).
734    Itemize,
735    /// APL `⍪`: the argument as a matrix — one row per item, that item's
736    /// elements ravelled. A scalar becomes 1×1, a vector n×1.
737    TableOf,
738    /// J `<` / APL `⊂`: the argument as one box.
739    Enclose(Enclose),
740    /// J `>` / APL `⊃`: open a box (rank 0, so the frame reassembles the
741    /// contents, filling where their shapes differ). A non-box opens to
742    /// itself.
743    Open,
744    /// J `;`: raze — the items of the opened boxes, catenated.
745    Raze,
746    /// APL `↑`: the first element, disclosed; the type's fill when there
747    /// is none.
748    First,
749    /// APL `∊`: enlist — every leaf element, in ravel order, as a vector.
750    Enlist,
751    /// APL `≡`: depth — 0 for a simple scalar, 1 for a simple array, one
752    /// more than the deepest content for a box.
753    Depth {
754        /// Negate the depth of an array whose items differ in depth or in
755        /// shape, as the Dyalog line does.
756        signed: bool,
757    },
758    /// J `I.` / APL `⍸`: index `i` repeated `y[i]` times. J applies at
759    /// rank 1; APL applies whole, and answers a rank-2-or-higher argument
760    /// with one boxed coordinate vector per occurrence.
761    Indices { origin: i64, boxed_coords: bool },
762    /// J `i:`: the integers from `-y` to `y`, one step apart.
763    Steps,
764    /// J `x:`: the argument in the exact types — extended when every value
765    /// is whole, rational otherwise.
766    ToExact,
767    /// J `p:`: the y-th prime, counting from zero.
768    NthPrime,
769    /// J `q:`: y's prime factors, ascending, with multiplicity.
770    PrimeFactors,
771    /// J `%.` / APL `⌹`: the inverse, or the least-squares pseudo-inverse.
772    MatrixInverse,
773    /// J `?` / `?.` and APL `?`: roll. Each element of y is replaced by a
774    /// random value below it, counted from `origin`. `fixed` restarts the
775    /// generator at its fixed seed, which is J's `?.`; `float_at_zero` is
776    /// J's `? 0`, a uniform double, where APL refuses a zero.
777    Roll { origin: i64, fixed: bool, float_at_zero: bool },
778    /// J `+. y` (rectangular) and `*. y` (polar): the two parts of a
779    /// complex number as a two-element vector, which becomes a new trailing
780    /// axis. A real argument is the pair `y 0` / `|y| 0`.
781    ComplexParts { polar: bool },
782    /// J `=`: self-classify — one row per distinct item, holding 1 where
783    /// that item stands among y's items.
784    SelfClassify,
785    /// J `~:` / APL `≠`: nub sieve — 1 at each item that has not occurred
786    /// before.
787    NubSieve,
788    /// J `u:` / APL `⎕UCS`: codepoints become characters, characters become
789    /// their codepoints. `pass_chars` is J's monad, which answers characters
790    /// with themselves rather than converting them.
791    Unicode { pass_chars: bool },
792    /// J `s:`: the argument's text as interned symbols. A character list
793    /// is cut on its own leading delimiter; a character table gives one
794    /// name per row; a boxed argument gives one name per box.
795    Symbols,
796    /// J `$.`: the argument in sparse form — every axis sparse, zero the
797    /// sparse element. A scalar has no axis to store along and stays dense.
798    Sparse,
799    /// J `;:`: J's own tokeniser over a character list, one box per word.
800    Words,
801    /// APL `⊆` (Dyalog): nest — enclose y unless it is already nested, or
802    /// a simple scalar, which cannot be enclosed any further.
803    Nest,
804    /// J `L.`: the boxing level — 0 for anything unboxed, one more than the
805    /// deepest content otherwise.
806    LevelOf,
807    /// J `{::`: y's box structure with every leaf replaced by the path that
808    /// fetches it — a boxed list holding one index per level descended.
809    MapPaths,
810    /// J `p.`: the roots of the polynomial whose ascending coefficients y
811    /// holds, as the boxed pair `multiplier ; roots`; a boxed argument of
812    /// that form converts back to coefficients.
813    PolyRoots,
814    /// J `p..`: the derivative of the polynomial y's ascending coefficients
815    /// describe, again as coefficients.
816    PolyDeriv,
817    /// J `A.`: the anagram index of the permutation y's items rank as.
818    AnagramIndex,
819    /// J `C.`: a direct permutation as its cycles, or a boxed list of
820    /// cycles as the direct permutation. The argument's type decides which.
821    CycleForm,
822    /// APL `↓`: split — each major cell of y enclosed, the leading axis
823    /// becoming the shape of the result.
824    Split,
825    /// J `". y` / APL `⍎ y`: compile the characters of y as a program of
826    /// this language and run it here, over the names the caller already
827    /// has. Nothing else about the sandbox changes: the nested program can
828    /// reach exactly what the outer one can.
829    Execute { apl: bool },
830    /// J `$.^:_1`: the obverse of sparse — the argument with every position
831    /// materialised. A dense argument is already the answer.
832    Dense,
833    /// J `p:^:_1`: the obverse of the y-th prime — how many primes stand
834    /// below y, which sends a prime back to its own index.
835    PrimeCount,
836    /// J `I.^:_1`: the obverse of indices — how many times each index from
837    /// zero to the largest occurs in y.
838    IndicesInverse,
839    /// Present in the language, not implemented: named feature.
840    NotYet(&'static str),
841    /// No monadic meaning exists for this primitive in its language.
842    None,
843}
844
845/// Dyadic meaning of a primitive.
846#[derive(Clone, Copy, Debug, PartialEq, Eq)]
847pub enum DyadOp {
848    Scalar(ScalarDyad),
849    /// x $ y / x ⍴ y: lay out shape x, reusing y — its ITEMS in J, its
850    /// ravel in APL.
851    Reshape,
852    /// x {. y / x ↑ y: per-axis take, negative from the end, overtake fills.
853    Take,
854    /// x }. y / x ↓ y: per-axis drop, negative from the end.
855    Drop,
856    /// y (APL `⊢`).
857    Right,
858    /// x (APL `⊣`).
859    Left,
860    /// `x |. y`: rotate axis k of y left by `x[k]` (negative rotates right).
861    Rotate,
862    /// `x ⌽ y` and `x ⊖ y`: rotate ONE axis of y — the last one when
863    /// `last`, the leading one otherwise — by one amount per vector along
864    /// it. APL's left argument is a whole array shaped like y with that
865    /// axis removed, not J's one amount per axis.
866    RotateApl { last: bool },
867    /// Catenate along the LEADING axis (J `,`, APL `⍪`).
868    AppendLeading,
869    /// Catenate along the LAST axis (APL `,`).
870    AppendLast,
871    /// x i. y / x ⍳ y: the index in x's items of each cell of y, or
872    /// `origin + #items(x)` when absent. `vector_left` is the Dyalog
873    /// dialect's rule that the left argument must be a vector.
874    IndexOf { origin: i64, vector_left: bool },
875    /// x e. y: is each cell of x, shaped like y's items, an item of y?
876    MemberJ,
877    /// x ∊ y: does each ELEMENT of x occur anywhere in y?
878    MemberApl,
879    /// x { y: each integer atom of x selects an item of y (negative from
880    /// the end).
881    From,
882    /// x -: y / x ≡ y: same shape and same values; never a shape error.
883    Match,
884    /// The negation of `Match` (APL `≢`).
885    NotMatch,
886    /// x /: y and x \: y: x's items reordered by the grade of y's items.
887    GradeSelect { down: bool },
888    /// `x # y` (J), `x/y` and `x⌿y` (APL): item i of y repeated `x[i]` times.
889    /// A one-element x applies to every item.
890    Copy,
891    /// `x #. y` / `x ⊥ y`: mixed-radix decode. A scalar x is the base for
892    /// every digit; otherwise x and y have the same length.
893    Decode,
894    /// `x #: y` / `x ⊤ y`: mixed-radix encode. The digits become the LEADING
895    /// axis of the result, which is what makes one operation serve J's
896    /// per-atom `#:` (right rank 0) and APL's `⊤` (right rank infinite).
897    Encode,
898    /// `x ⍋ y` and `x ⍒ y`: the items of y graded by where each of their
899    /// characters sits in the collating array x.
900    CollateGrade { down: bool, origin: i64 },
901    /// `x |: y`: y with the named axes moved to the end. A boxed x groups
902    /// axes to be run together, which is the diagonal.
903    TransposeJ,
904    /// `x ⍉ y`: x says, for each axis of y, which axis of the result it
905    /// becomes; a repeated destination runs those axes together.
906    TransposeApl,
907    /// `x ⊥ y` on arguments of rank 2 and above: the inner product `+.×`
908    /// over the LAST axis of x and the LEADING axis of y.
909    DecodeApl,
910    /// `x ⊤ y` where x has rank 2 or more: x's LEADING axis is the radix,
911    /// and its remaining axes frame the result along with y's.
912    EncodeApl,
913    /// `x ,: y`: the two arguments as the items of a new leading axis.
914    Laminate,
915    /// J `;`: link — `(<x)` before y, which is taken as it is when it is
916    /// already boxed and boxed when it is not.
917    Link,
918    /// APL vector notation: x is one more item in front of the strand y.
919    Strand,
920    /// J `x I. y` / APL `x ⍸ y`: which interval of the ascending x each cell
921    /// of y falls in. The field is what the language adds to the count of
922    /// items below it: nothing in J, `⎕IO - 1` in APL.
923    IntervalIndex { offset: i64, closed: bool },
924    /// J `x i: y`: where each cell of y LAST sits among the items of x.
925    IndexOfLast { origin: i64 },
926    /// J `x %. y` / APL `x ⌹ y`: the least-squares solution of `y a = x`.
927    MatrixDivide,
928    /// APL `x ⊂ y`: partitioned enclose — a 1 in x opens a partition, a 0
929    /// continues it, and a leading run of 0s drops those items.
930    PartitionEnclose,
931    /// Dyalog's partitioned enclose: the left argument counts the
932    /// partitions to open before each item, rather than flagging where
933    /// one begins.
934    PartitionCounts,
935    /// APL `x ⌷ y`: one scalar index per axis of y.
936    Squad {
937        origin: i64,
938        /// Read the index as one item per LEADING axis, so fewer items
939        /// than the rank take the trailing axes whole (the Dyalog line).
940        /// Otherwise there is one item per axis, all of them named.
941        leading: bool,
942    },
943    /// One bracket slot of APL indexing: axis `axis` of y selected by x.
944    /// `rank`, when it is not zero, is the number of slots the brackets
945    /// held, checked by the slot that sees the whole array.
946    SelectAxis { axis: usize, rank: usize, origin: i64 },
947    /// J `x {:: y`: follow the path x into y, opening a level a step.
948    Fetch,
949    /// J `x p. y`: the polynomial with ascending coefficients x at y. A
950    /// boxed x is the `multiplier ; roots` form of the same polynomial.
951    PolyEval,
952    /// J `x p.. y`: the integral of the polynomial y's coefficients
953    /// describe, with x as the constant term.
954    PolyIntegral,
955    /// APL `x ⍕ y`: format by specification — one width and precision per
956    /// column of the last axis, or one pair for the whole argument.
957    FormatSpec,
958    /// J `x ": y`: format by specification — one `w j d` complex value per
959    /// column of the last axis, or one for the whole argument. A negative
960    /// width asks for the exponential form; a value that does not fit its
961    /// field is written as asterisks.
962    FormatSpecJ,
963    /// J `x ". y`: the numbers a line of text spells, with x standing in
964    /// for every word that is not one.
965    ParseNumbers,
966    /// J `x ;: y`: the sequential machine x describes, run over y.
967    SequentialMachine,
968    /// J `x m b. y`: the boolean function whose truth table `m` numbers,
969    /// on two bits for `m` below 16 and on every bit of two integers for
970    /// `m` from 16 to 31.
971    TruthTable(u8),
972    /// J `x x: y`: which exact form. 1 is the rational one, 2 the pair of
973    /// numerator and denominator, `_1` the conversion back to a machine
974    /// number, `_2` the argument unchanged.
975    ExactForm,
976    /// J `x ? y` / `x ?. y` and APL `x ? y`: deal — x distinct values from
977    /// the y below `origin + y`.
978    Deal { origin: i64, fixed: bool },
979    /// J `+:` and `*:` / APL `⍱` and `⍲`: the two boolean operations that
980    /// have no other reading. Both arguments must be 0 or 1.
981    Boolean(BoolDyad),
982    /// J `x -. y` / APL `x ~ y`: the items of x that are not items of y.
983    Less,
984    /// APL `x ∪ y`: x's items, then y's items that x does not already have.
985    Union,
986    /// APL `x ∩ y`: the items of x that y also has, in x's order.
987    Intersect,
988    /// J `x A. y`: y's items under the x-th permutation of the items, the
989    /// permutations counted in lexicographic order.
990    AnagramFrom,
991    /// J `x C. y`: y's items permuted by x — a direct permutation, or a
992    /// boxed list of cycles.
993    Permute,
994    /// J `x E. y` / APL `x ⍷ y`: 1 at each position of y where a copy of x
995    /// begins.
996    FindSeq,
997    /// J `x u: y`: which conversion — 3 and 4 take characters to
998    /// codepoints, 8 and 10 take codepoints to characters.
999    UnicodeForm,
1000    /// J `x p: y`: which fact about primes — `_1` counts the primes below
1001    /// y, 0 asks whether y is composite, 1 whether it is prime, and `x` of
1002    /// magnitude 4 steps to the next or previous prime.
1003    PrimeMeta,
1004    /// J `x q: y`: the exponents of the first x primes in y, or, for `__`,
1005    /// the distinct primes over their exponents as a 2-row table.
1006    PrimeExponents,
1007    /// J `x s:`: the numbered symbol forms. 4 gives the names as a padded
1008    /// character table, 5 gives them as boxes.
1009    SymbolForm,
1010    /// APL `x ⊃ y`: pick — follow the path x into y, opening a level a step.
1011    Pick { origin: i64 },
1012    /// APL `x \ y` and `x ⍀ y`: expand — a 1 in x takes the next item of y,
1013    /// a 0 puts a fill in its place.
1014    Expand,
1015    /// J `x 1!:2 y`: write x, formatted as it displays and followed by a
1016    /// newline, to the stream y; the value is x. Stream 2 is stdout, which
1017    /// the sandbox opens, and everything else is a file, which it does not.
1018    WriteStream,
1019    /// J `x $.`: the numbered sparse forms. `_1` gives the shape, the
1020    /// sparse axes and the sparse element boxed; 0 converts between the two
1021    /// storage kinds; 1 makes a new sparse array from a shape; 2 to 5 and 7
1022    /// ask about the argument; 8 drops the stored entries that hold the
1023    /// sparse element.
1024    SparseForm,
1025    NotYet(&'static str),
1026    None,
1027}
1028
1029/// The dyadic operations that read and write booleans and nothing else.
1030#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1031pub enum BoolDyad {
1032    /// J `+:`, APL `⍱`: neither.
1033    Nor,
1034    /// J `*:`, APL `⍲`: not both.
1035    Nand,
1036}
1037
1038/// A primitive verb: a name for diagnostics, both valence meanings, and
1039/// J-style ranks [monadic, dyadic-left, dyadic-right].
1040#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1041pub struct Prim {
1042    pub name: &'static str,
1043    pub monad: MonadOp,
1044    pub dyad: DyadOp,
1045    pub ranks: [i64; 3],
1046}
1047
1048/// Which windowed application a [`Verb::Windowed`] performs. One variant
1049/// covers all three because the work is the same: the verb is applied to a
1050/// run of consecutive items, and only the choice of runs differs.
1051#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1052pub enum WindowKind {
1053    /// J `u\`: the monad applies u to every prefix, the dyad `x u\ y` to
1054    /// every window of x items.
1055    Prefix,
1056    /// J `u\.`: the monad applies u to every suffix; the dyad (outfix) is
1057    /// not implemented.
1058    Suffix,
1059    /// APL `f\` and `f⍀`: the monad is the scan, which is the prefix
1060    /// application. APL has no dyadic scan — `x\y` is expand, a function of
1061    /// its own — so the dyad reports that instead.
1062    Scan,
1063}
1064
1065/// How many times a [`Verb::PowerN`] applies its verb.
1066#[derive(Clone, Debug, PartialEq, Eq)]
1067pub enum Power {
1068    /// Exactly `n` applications; 0 is the identity.
1069    Times(u64),
1070    /// Iterate until a result matches the one before it (J `u^:_`).
1071    Converge,
1072    /// A list of counts: one answer per count, framed (`u^:(0 1 2)`). A
1073    /// boxed count is spelled this way too — `u^:(<n)` is `u^:(i.n)`.
1074    Each(Vec<u64>),
1075    /// Every result on the way to convergence, framed (`u^:a:`).
1076    ConvergeTrace,
1077}
1078
1079/// Iterations `Power::Converge` allows before giving up.
1080const CONVERGE_LIMIT: usize = 1 << 20;
1081
1082/// The results `u M.` has already computed, keyed by the arguments that
1083/// produced them. Shared by every clone of the derived verb, which is what
1084/// makes the cache survive from one application to the next.
1085pub type MemoCache = Arc<std::sync::Mutex<HashMap<Vec<u64>, Array>>>;
1086
1087/// What a user-written operator was given for an operand.
1088///
1089/// Dyalog lets an ARRAY stand where a function operand belongs, and the
1090/// body then reads `⍺⍺` or `⍵⍵` as that array: `2{⍺⍺+⍵}3` is 5.
1091#[derive(Clone, Debug)]
1092pub enum Operand {
1093    Func(Box<Verb>),
1094    Value(Box<Array>),
1095}
1096
1097impl Operand {
1098    /// Name for diagnostics.
1099    pub fn name(&self) -> String {
1100        match self {
1101            Operand::Func(v) => v.name(),
1102            Operand::Value(_) => "n".to_string(),
1103        }
1104    }
1105
1106    fn is_value(&self) -> bool {
1107        matches!(self, Operand::Value(_))
1108    }
1109}
1110
1111/// An operator dfn's body, parsed once for each reading of its operands.
1112///
1113/// Whether `⍺⍺` names a function or an array decides how the body PARSES,
1114/// not merely what it computes: `⍺⍺+⍵` is a train under the first reading
1115/// and a sum under the second. The body is therefore parsed both ways —
1116/// four ways when it takes a right operand as well — when the dfn is
1117/// defined, and the operands choose the reading when they arrive.
1118#[derive(Debug)]
1119pub struct OpDef {
1120    /// Indexed by `(⍺⍺ is an array) + 2 × (⍵⍵ is an array)`. `Err` holds
1121    /// what the body said when it would not parse that way, so choosing
1122    /// that reading reports the body's own complaint.
1123    pub readings: [std::result::Result<Verb, String>; 4],
1124}
1125
1126impl OpDef {
1127    /// The one reading with a body under every combination of operands:
1128    /// what a dfn that mentions neither `⍺⍺` nor `⍵⍵` would need, and the
1129    /// shape a frontend uses before it has parsed the alternatives.
1130    pub fn uniform(v: Verb) -> OpDef {
1131        OpDef { readings: [Ok(v.clone()), Ok(v.clone()), Ok(v.clone()), Ok(v)] }
1132    }
1133
1134    /// The body as it parses for these operands.
1135    pub fn pick(&self, alpha: &Operand, omega: Option<&Operand>) -> Result<&Verb> {
1136        let i = usize::from(alpha.is_value())
1137            | (usize::from(omega.is_some_and(Operand::is_value)) << 1);
1138        self.readings[i].as_ref().map_err(|msg| Error::new(ErrorKind::Parse, msg.clone(), None))
1139    }
1140
1141    /// Every reading that parsed, for the questions asked of the derived
1142    /// verb before its operands have chosen one.
1143    fn bodies(&self) -> impl Iterator<Item = &Verb> {
1144        self.readings.iter().filter_map(|r| r.as_ref().ok())
1145    }
1146}
1147
1148/// A verb: primitive or derived. Language-agnostic; frontends decide which
1149/// combinations their syntax produces (e.g. APL `+/` becomes
1150/// `Rank(Reduce(+), [1,1,1])` — reduce the last axis).
1151#[derive(Clone, Debug)]
1152pub enum Verb {
1153    Prim(Prim),
1154    /// Apply the verb to cells of the given ranks (J `"`, APL `⍤`).
1155    Rank(Box<Verb>, [i64; 3]),
1156    /// Insert the verb between items, folding right to left (J `/`, APL `⌿`).
1157    Reduce(Box<Verb>),
1158    /// APL `f/` and `f⌿`: the same insert monadically, and the N-WISE
1159    /// REDUCTION dyadically — `n f/ y` folds each window of n items along
1160    /// the leading axis. J's `u/` is the table dyadically, so the two
1161    /// spellings cannot share a node.
1162    NWise(Box<Verb>),
1163    /// Apply the verb to runs of consecutive items (J `\` and `\.`, APL
1164    /// `\` and `⍀`). The valence chooses the runs; see [`WindowKind`].
1165    Windowed(Box<Verb>, WindowKind),
1166    /// J `u~`, APL `u⍨`: monad `u~ y` = `y u y`; dyad `x u~ y` = `y u x`.
1167    Commute(Box<Verb>),
1168    /// J `u^:n`, APL `u⍣n`: apply the verb n times, or to convergence.
1169    PowerN(Box<Verb>, Power),
1170    /// (f g h) y = (f y) g (h y);  x (f g h) y = (x f y) g (x h y).
1171    Fork(Box<Verb>, Box<Verb>, Box<Verb>),
1172    /// (n g h) y = n g (h y);  x (n g h) y = n g (x h y).
1173    NounFork(Array, Box<Verb>, Box<Verb>),
1174    /// (f g) y = y f (g y);  x (f g) y = x f (g y).  (J hook)
1175    Hook(Box<Verb>, Box<Verb>),
1176    /// f@:g / [: f g:  monad f (g y);  dyad f (x g y).
1177    Atop(Box<Verb>, Box<Verb>),
1178    /// f&:g:  monad f (g y);  dyad (g x) f (g y). J's `&` is this wrapped in
1179    /// [`Verb::Rank`] at g's monadic rank; `&:` is this on its own.
1180    Compose(Box<Verb>, Box<Verb>),
1181    /// `m&v`: the noun bonded as the left argument — monad `m v y`. J gives
1182    /// a bond no dyadic valence at all.
1183    BondLeft(Array, Box<Verb>),
1184    /// `u&n`: the noun bonded as the right argument — monad `y u n`.
1185    BondRight(Box<Verb>, Array),
1186    /// J `u&.>` and APL `u¨`: open each box, apply u, put the result back
1187    /// in a box. Cell rank 0 on every side, so the frames pair as usual.
1188    Each(Box<Verb>, Enclose),
1189    /// J `u&.,`: the other under that is not built out of an inverse. `,`
1190    /// has no obverse of its own — a ravel says nothing about the shape it
1191    /// came from — but under a FIXED shape it has one, so `u&., y` is u
1192    /// over the ravel, reshaped to y's own shape. The reference gives it
1193    /// one valence only.
1194    UnderRavel(Box<Verb>),
1195    /// J `u!.n`: apply u with the comparison tolerance replaced by n.
1196    Fit(Box<Verb>, f64),
1197    /// J `x m} y`: y with the items at the indices m replaced by x.
1198    Amend(Array),
1199    /// J `u}`: the same amend, with the indices computed rather than
1200    /// written — `u} y` is `(u y)} y` and `x u} y` is `x (x u y)} y`.
1201    AmendVerb(Box<Verb>),
1202    /// J `|.!.f`: shift instead of rotate, the vacated positions taking the
1203    /// fill f.
1204    ShiftFill(Array),
1205    /// J `u M.`: u, with the results it has already computed kept and
1206    /// returned again for the same arguments. The cache belongs to this
1207    /// derived verb, so it lives exactly as long as the program does.
1208    Memo(Box<Verb>, MemoCache),
1209    /// J `u L: n` and `u S: n`: apply u to every subarray at boxing level
1210    /// n or below. `L:` puts each result back where its operand was; `S:`
1211    /// spreads them into the items of one array.
1212    Level { u: Box<Verb>, level: i64, spread: bool },
1213    /// J `u b.`: answers questions about u rather than applying it. `0` asks
1214    /// for its three ranks.
1215    Characteristics(Box<Verb>),
1216    /// APL `f⍛g` (before): g's LEFT argument is prepared by f — monad
1217    /// `(f y) g y`, dyad `(f x) g y`. The mirror of [`Verb::Beside`].
1218    Before(Box<Verb>, Box<Verb>),
1219    /// APL `f OP` and `f OP g`: a dfn that mentions `⍺⍺` or `⍵⍵` is an
1220    /// OPERATOR, and this is that operator with its operands supplied. They
1221    /// are bound under those two names for as long as the body runs.
1222    UserDerived { def: Arc<OpDef>, alpha: Operand, omega: Option<Operand> },
1223    /// APL `f⌸` (key, Dyalog): the major cells are grouped by value, and f
1224    /// is applied to each key and the group that shares it. Monadically the
1225    /// group is the positions the key occupies; dyadically it is the items
1226    /// of the right argument at those positions.
1227    KeyPairs(Box<Verb>),
1228    /// J `u/.`: the key dyadically (u over each group of items sharing a
1229    /// key), the oblique monadically (u over each anti-diagonal).
1230    Key(Box<Verb>),
1231    /// J `u;.n`: cut — u over the intervals a fret marks out.
1232    Cut(Box<Verb>, i64),
1233    /// J `u^:v`: v's value at the arguments is the number of applications.
1234    PowerV(Box<Verb>, Box<Verb>),
1235    /// APL `f⍣g`: apply f until `new g old` holds.
1236    PowerUntil(Box<Verb>, Box<Verb>),
1237    /// APL `f[k]`: f along axis k. The axis is brought to the front, f
1238    /// applies to the leading axis, and a result of the argument's own rank
1239    /// has the axis put back where it was.
1240    AlongAxis(Box<Verb>, usize),
1241    /// An explicit definition: a body of sentences run with the arguments
1242    /// bound to names. J's `3 : '…'`, `4 : '…'` and `{{ … }}`, APL's `{…}`
1243    /// and `∇`-defined functions.
1244    Explicit(Arc<crate::ir::ExplicitDef>),
1245    /// J `$:`, APL `∇`: the definition lexically containing the reference,
1246    /// found at run time as the innermost one then running.
1247    SelfRef,
1248    /// A verb named earlier in the program, looked up when it is applied so
1249    /// that a definition can call itself by its own name.
1250    Named(String),
1251    /// J `u :. v`: u, with v declared to be its obverse. The declaration is
1252    /// what `obverse` answers with; applying the verb applies u.
1253    WithObverse(Box<Verb>, Box<Verb>),
1254    /// J `m@.v`: agenda — v's value at the arguments picks which of the
1255    /// gerund's verbs to apply.
1256    Agenda(Vec<Verb>, Box<Verb>),
1257    /// J `u :: v`: adverse — apply u, and if the language refuses it, apply
1258    /// v to the same arguments instead. A gap in libjay is not an error the
1259    /// program may handle, and goes straight through.
1260    Adverse(Box<Verb>, Box<Verb>),
1261    /// J `m H. n`: the generalised hypergeometric function, summed as a
1262    /// series over the numerator parameters m and the denominator ones n.
1263    Hypergeometric { num: Vec<crate::complex::Cx>, den: Vec<crate::complex::Cx> },
1264    /// APL `f∘g` (beside): monad `f (g y)`, dyad `x f (g y)`. g prepares the
1265    /// right argument and the left one arrives untouched, which is what
1266    /// separates it from `⍥` (this crate's [`Verb::Compose`]).
1267    Beside(Box<Verb>, Box<Verb>),
1268    /// APL `f⌺w` (Dyalog's stencil): f applied to the window of `w` cells
1269    /// centred on each cell of y in turn, the edges filled. One size per
1270    /// leading axis; the axes past them travel with the cell.
1271    Stencil(Box<Verb>, Vec<i64>),
1272    /// J `` m`:n `` for the two forms that are not a train: `0` applies
1273    /// every verb of the gerund to the arguments and frames the answers,
1274    /// `3` inserts the verbs between the items of y, cycling through them
1275    /// left to right and folding right to left. `` `:6 `` is a train and is
1276    /// built at parse time, so it never reaches here.
1277    Evoke(Vec<Verb>, i64),
1278    /// J `u . v` and APL `f.g`: the inner product, of which `+/ . *` and
1279    /// `+.×` are the matrix product. Dyadically each cell of x at v's
1280    /// dyadic LEFT rank — 1 where that rank is smaller — meets the whole
1281    /// of y under v, and u folds what comes back. Monadically, which is
1282    /// J's alone, it is the determinant by minors down the first column:
1283    /// `-/ . *` is the determinant proper.
1284    InnerProduct { u: Box<Verb>, v: Box<Verb>, apl: bool },
1285}
1286
1287impl Verb {
1288    /// [monadic, dyadic-left, dyadic-right] ranks governing cell iteration.
1289    pub fn ranks(&self) -> [i64; 3] {
1290        match self {
1291            Verb::Prim(p) => p.ranks,
1292            Verb::Rank(_, r) => *r,
1293            // `x u\ y` and `x u\. y` take one width per application, so the
1294            // left cell is an atom: a list of widths frames the result, as
1295            // in J, and an empty list of them frames nothing.
1296            Verb::Windowed(_, WindowKind::Prefix | WindowKind::Suffix) => {
1297                [RANK_INF, 0, RANK_INF]
1298            }
1299            Verb::Each(..) => [0, 0, 0],
1300            Verb::Fit(v, _) => v.ranks(),
1301            // Amend reads the whole argument, and the rest run their own
1302            // verb over the argument as a whole.
1303            Verb::Amend(_)
1304            | Verb::AmendVerb(_)
1305            | Verb::ShiftFill(_)
1306            | Verb::Level { .. }
1307            | Verb::Characteristics(_)
1308            | Verb::UserDerived { .. }
1309            | Verb::KeyPairs(_)
1310            | Verb::Key(_)
1311            | Verb::Cut(..)
1312            | Verb::PowerV(..)
1313            | Verb::PowerUntil(..)
1314            | Verb::AlongAxis(..) => [RANK_INF, RANK_INF, RANK_INF],
1315            Verb::Memo(v, _) => v.ranks(),
1316            Verb::WithObverse(v, _) | Verb::Adverse(v, _) => v.ranks(),
1317            Verb::Beside(..) => [RANK_INF, RANK_INF, RANK_INF],
1318            // The series is summed for one value at a time.
1319            Verb::Hypergeometric { .. } => [0, 0, 0],
1320            // The determinant is over a table; the dyad reads both
1321            // arguments whole and takes their cells itself.
1322            Verb::InnerProduct { .. } => [2, RANK_INF, RANK_INF],
1323            _ => [RANK_INF, RANK_INF, RANK_INF],
1324        }
1325    }
1326
1327    /// Name for diagnostics, e.g. `+/"1`.
1328    pub fn name(&self) -> String {
1329        match self {
1330            Verb::Prim(p) => p.name.to_string(),
1331            Verb::Rank(v, r) => format!("{}\"{}", v.name(), rank_str(*r)),
1332            Verb::Reduce(v) | Verb::NWise(v) => format!("{}/", v.name()),
1333            Verb::Windowed(v, WindowKind::Suffix) => format!("{}\\.", v.name()),
1334            Verb::Windowed(v, _) => format!("{}\\", v.name()),
1335            Verb::Commute(v) => format!("{}~", v.name()),
1336            Verb::PowerN(v, Power::Converge) => format!("{}^:_", v.name()),
1337            Verb::PowerN(v, Power::Times(n)) => format!("{}^:{n}", v.name()),
1338            Verb::PowerN(v, Power::Each(_)) => format!("{}^:n", v.name()),
1339            Verb::PowerN(v, Power::ConvergeTrace) => format!("{}^:a:", v.name()),
1340            Verb::Fork(f, g, h) => format!("({} {} {})", f.name(), g.name(), h.name()),
1341            Verb::NounFork(_, g, h) => format!("(n {} {})", g.name(), h.name()),
1342            Verb::Hook(f, g) => format!("({} {})", f.name(), g.name()),
1343            Verb::Atop(f, g) => format!("({}@:{})", f.name(), g.name()),
1344            Verb::Compose(f, g) => format!("({}&:{})", f.name(), g.name()),
1345            Verb::BondLeft(_, v) => format!("(n&{})", v.name()),
1346            Verb::BondRight(v, _) => format!("({}&n)", v.name()),
1347            Verb::UnderRavel(v) => format!("({}&.,)", v.name()),
1348            Verb::Each(v, Enclose::Always) => format!("({}&.>)", v.name()),
1349            Verb::Each(v, _) => format!("({}¨)", v.name()),
1350            Verb::Fit(v, n) => format!("{}!.{n}", v.name()),
1351            Verb::Amend(_) => "(m})".to_string(),
1352            Verb::AmendVerb(v) => format!("({}}})", v.name()),
1353            Verb::ShiftFill(_) => "|.!.n".to_string(),
1354            Verb::Characteristics(v) => format!("{} b.", v.name()),
1355            Verb::Before(f, g) => format!("({}⍛{})", f.name(), g.name()),
1356            Verb::KeyPairs(v) => format!("{}⌸", v.name()),
1357            Verb::UserDerived { alpha, omega, .. } => match omega {
1358                Some(g) => format!("({} {{…}} {})", alpha.name(), g.name()),
1359                None => format!("({} {{…}})", alpha.name()),
1360            },
1361            Verb::Memo(v, _) => format!("{} M.", v.name()),
1362            Verb::Level { u, level, spread } => {
1363                format!("{} {} {level}", u.name(), if *spread { "S:" } else { "L:" })
1364            }
1365            Verb::Key(v) => format!("{}/.", v.name()),
1366            Verb::Cut(v, n) => format!("{};.{n}", v.name()),
1367            Verb::PowerV(v, w) => format!("{}^:{}", v.name(), w.name()),
1368            Verb::PowerUntil(v, w) => format!("{}⍣{}", v.name(), w.name()),
1369            Verb::AlongAxis(v, k) => format!("{}[{k}]", v.name()),
1370            Verb::Explicit(d) => d.name.clone(),
1371            Verb::SelfRef => "$:".to_string(),
1372            Verb::Named(n) => n.clone(),
1373            Verb::WithObverse(v, w) => format!("({}:.{})", v.name(), w.name()),
1374            Verb::Adverse(v, w) => format!("({}::{})", v.name(), w.name()),
1375            Verb::Beside(f, g) => format!("({}∘{})", f.name(), g.name()),
1376            Verb::Hypergeometric { num, den } => {
1377                format!("({} H. {})", cx_list(num), cx_list(den))
1378            }
1379            Verb::Agenda(vs, w) => {
1380                let names: Vec<String> = vs.iter().map(Verb::name).collect();
1381                format!("({}@.{})", names.join("`"), w.name())
1382            }
1383            Verb::Evoke(vs, n) => {
1384                let names: Vec<String> = vs.iter().map(Verb::name).collect();
1385                format!("({}`:{n})", names.join("`"))
1386            }
1387            Verb::Stencil(u, w) => {
1388                let sizes: Vec<String> = w.iter().map(i64::to_string).collect();
1389                format!("({}⌺{})", u.name(), sizes.join(" "))
1390            }
1391            Verb::InnerProduct { u, v, .. } => format!("({} . {})", u.name(), v.name()),
1392        }
1393    }
1394
1395    /// True when the verb's meaning depends on the comparison tolerance —
1396    /// the comparisons, the searches that use them, and the two roundings.
1397    /// `u!.n` is only the tolerance conjunction for these; on anything else
1398    /// J's `!.` specifies a fill instead, which is a separate feature.
1399    pub fn uses_tolerance(&self) -> bool {
1400        match self {
1401            Verb::Prim(p) => {
1402                matches!(
1403                    p.monad,
1404                    MonadOp::Scalar(ScalarMonad::Floor)
1405                        | MonadOp::Scalar(ScalarMonad::Ceil)
1406                        | MonadOp::Nub
1407                        | MonadOp::GradeUp { .. }
1408                        | MonadOp::GradeDown { .. }
1409                        | MonadOp::EncodeBits
1410                ) || matches!(
1411                    p.dyad,
1412                    DyadOp::Scalar(
1413                        ScalarDyad::Eq
1414                            | ScalarDyad::Ne
1415                            | ScalarDyad::Lt
1416                            | ScalarDyad::Le
1417                            | ScalarDyad::Gt
1418                            | ScalarDyad::Ge
1419                            | ScalarDyad::Residue
1420                            | ScalarDyad::Gcd
1421                            | ScalarDyad::Lcm
1422                    ) | DyadOp::Match
1423                        | DyadOp::GradeSelect { .. }
1424                        | DyadOp::Encode
1425                        | DyadOp::EncodeApl
1426                        | DyadOp::NotMatch
1427                        | DyadOp::MemberJ
1428                        | DyadOp::MemberApl
1429                        | DyadOp::IndexOf { .. }
1430                        | DyadOp::IndexOfLast { .. }
1431                )
1432            }
1433            Verb::Rank(v, _)
1434            | Verb::Reduce(v)
1435            | Verb::NWise(v)
1436            | Verb::Windowed(v, _)
1437            | Verb::Commute(v)
1438            | Verb::PowerN(v, _)
1439            | Verb::BondLeft(_, v)
1440            | Verb::BondRight(v, _)
1441            | Verb::Each(v, _)
1442            | Verb::UnderRavel(v)
1443            | Verb::Fit(v, _)
1444            | Verb::Key(v)
1445            | Verb::Cut(v, _)
1446            | Verb::AlongAxis(v, _) => v.uses_tolerance(),
1447            Verb::PowerV(v, w) | Verb::PowerUntil(v, w) => {
1448                v.uses_tolerance() || w.uses_tolerance()
1449            }
1450            // An explicit definition's body is a program of its own; `!.`
1451            // has no reach into it.
1452            Verb::Amend(_)
1453            | Verb::AmendVerb(_)
1454            | Verb::ShiftFill(_)
1455            | Verb::Characteristics(_)
1456            | Verb::Explicit(_)
1457            | Verb::SelfRef
1458            | Verb::Named(_)
1459            | Verb::Hypergeometric { .. } => false,
1460            Verb::Memo(v, _) | Verb::Level { u: v, .. } => v.uses_tolerance(),
1461            Verb::WithObverse(v, _) => v.uses_tolerance(),
1462            Verb::Adverse(v, w) | Verb::Beside(v, w) | Verb::Before(v, w) => {
1463                v.uses_tolerance() || w.uses_tolerance()
1464            }
1465            Verb::KeyPairs(v) => v.uses_tolerance(),
1466            Verb::UserDerived { def, alpha, omega } => {
1467                let operand = |o: &Operand| match o {
1468                    Operand::Func(v) => v.uses_tolerance(),
1469                    Operand::Value(_) => false,
1470                };
1471                def.bodies().any(Verb::uses_tolerance)
1472                    || operand(alpha)
1473                    || omega.as_ref().is_some_and(operand)
1474            }
1475            Verb::Agenda(vs, w) => {
1476                w.uses_tolerance() || vs.iter().any(Verb::uses_tolerance)
1477            }
1478            Verb::Evoke(vs, _) => vs.iter().any(Verb::uses_tolerance),
1479            Verb::Stencil(u, _) => u.uses_tolerance(),
1480            Verb::InnerProduct { u, v, .. } => u.uses_tolerance() || v.uses_tolerance(),
1481            Verb::Fork(f, g, h) => {
1482                f.uses_tolerance() || g.uses_tolerance() || h.uses_tolerance()
1483            }
1484            Verb::NounFork(_, g, h)
1485            | Verb::Hook(g, h)
1486            | Verb::Atop(g, h)
1487            | Verb::Compose(g, h) => g.uses_tolerance() || h.uses_tolerance(),
1488        }
1489    }
1490
1491    /// True when applying this verb does nothing beyond producing its
1492    /// result. Output (`echo`, `⎕←`) is the only effect a verb can have, and
1493    /// only a pure verb may have its cells run out of order on several
1494    /// threads. Deliberately conservative: a new effect must be added here.
1495    pub fn is_pure(&self) -> bool {
1496        match self {
1497            // Output and the random source are the two effects a verb can
1498            // have; both fix the order its cells must run in.
1499            Verb::Prim(p) => {
1500                !matches!(
1501                    p.monad,
1502                    MonadOp::Echo | MonadOp::Roll { .. } | MonadOp::ReadStream
1503                ) && !matches!(p.dyad, DyadOp::Deal { .. } | DyadOp::WriteStream)
1504            }
1505            Verb::Rank(v, _)
1506            | Verb::Reduce(v)
1507            | Verb::NWise(v)
1508            | Verb::Windowed(v, _)
1509            | Verb::Commute(v)
1510            | Verb::PowerN(v, _) => v.is_pure(),
1511            Verb::Fork(f, g, h) => f.is_pure() && g.is_pure() && h.is_pure(),
1512            Verb::NounFork(_, g, h)
1513            | Verb::Hook(g, h)
1514            | Verb::Atop(g, h)
1515            | Verb::Compose(g, h) => g.is_pure() && h.is_pure(),
1516            Verb::BondLeft(_, v)
1517            | Verb::BondRight(v, _)
1518            | Verb::Each(v, _)
1519            | Verb::UnderRavel(v)
1520            | Verb::Fit(v, _) => v.is_pure(),
1521            Verb::Key(v) | Verb::Cut(v, _) | Verb::AlongAxis(v, _) => v.is_pure(),
1522            Verb::Hypergeometric { .. } => true,
1523            Verb::PowerV(v, w) | Verb::PowerUntil(v, w) => v.is_pure() && w.is_pure(),
1524            Verb::WithObverse(v, _) => v.is_pure(),
1525            Verb::Adverse(v, w) | Verb::Beside(v, w) | Verb::Before(v, w) => {
1526                v.is_pure() && w.is_pure()
1527            }
1528            Verb::KeyPairs(v) => v.is_pure(),
1529            // The body reads and writes the program's names, exactly as a
1530            // definition called any other way does.
1531            Verb::UserDerived { .. } => false,
1532            Verb::Agenda(vs, w) => w.is_pure() && vs.iter().all(Verb::is_pure),
1533            Verb::Evoke(vs, _) => vs.iter().all(Verb::is_pure),
1534            Verb::Stencil(u, _) => u.is_pure(),
1535            Verb::InnerProduct { u, v, .. } => u.is_pure() && v.is_pure(),
1536            Verb::Amend(_) | Verb::ShiftFill(_) | Verb::Characteristics(_) => true,
1537            Verb::AmendVerb(v) | Verb::Level { u: v, .. } => v.is_pure(),
1538            // A memo answers from its cache, so the verb inside it must be
1539            // pure for the cache to be an optimisation rather than a change
1540            // of meaning; running the cells in any order is then safe too.
1541            Verb::Memo(v, _) => v.is_pure(),
1542            // An explicit definition reads and writes the program's names,
1543            // so its cells can never be run out of order on other threads —
1544            // whatever its body does. `ExplicitDef::pure` records whether
1545            // the body itself has an effect; this is the stronger question.
1546            Verb::Explicit(_) | Verb::SelfRef | Verb::Named(_) => false,
1547        }
1548    }
1549
1550    /// Whether this verb reads a sparse argument in its stored form.
1551    ///
1552    /// The set is small on purpose: `$.` itself, the two verbs that ask
1553    /// about an array rather than about its elements, and the three that
1554    /// draw it. Everything else is handed the dense expansion, which is the
1555    /// same value — the storage kind is not visible in the answer, only in
1556    /// how long it took to get there.
1557    fn monad_reads_sparse(&self) -> bool {
1558        let Verb::Prim(p) = self else { return false };
1559        matches!(
1560            p.monad,
1561            MonadOp::Sparse
1562                | MonadOp::ShapeOf
1563                | MonadOp::Tally
1564                | MonadOp::TypeCode
1565                | MonadOp::Format
1566                | MonadOp::Echo
1567        )
1568    }
1569
1570    /// Whether this verb reads a sparse RIGHT argument in its stored form.
1571    /// A sparse left argument is always expanded: no dyad reads one.
1572    fn dyad_reads_sparse(&self) -> bool {
1573        matches!(self, Verb::Prim(p) if p.dyad == DyadOp::SparseForm)
1574    }
1575
1576    /// Full monadic application including rank/frame machinery.
1577    ///
1578    /// This is one of the two places a column-major argument is dealt with:
1579    /// the verbs that read one natively get it as it lies, and every other
1580    /// verb gets the rows it assumes, materialised once here.
1581    pub fn monad(&self, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1582        let _depth = Nesting::enter(span)?;
1583        // Every application decides its own shyness, and only an explicit
1584        // definition's last sentence makes it shy. An operator that ends
1585        // by applying its operand therefore keeps what the operand left —
1586        // `{a←⍵}¨1 2 3` is shy — and a primitive over the same value does
1587        // not.
1588        ctx.shy = false;
1589        // A verb that does not read the stored form gets the array every
1590        // position of it materialised, which is the same value.
1591        let dense;
1592        let y = if y.is_sparse() && !self.monad_reads_sparse() {
1593            dense = y.densified();
1594            &dense
1595        } else {
1596            y
1597        };
1598        if y.is_row_major() {
1599            return self.monad_rows(y, ctx, span);
1600        }
1601        match self.monad_columns(y, ctx, span) {
1602            Some(r) => r,
1603            None => self.monad_rows(&y.to_row_major(), ctx, span),
1604        }
1605    }
1606
1607    /// Monadic application to an argument whose buffer is row-major, which
1608    /// is what everything below assumes.
1609    fn monad_rows(&self, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1610        debug_assert!(y.is_row_major());
1611        match self {
1612            Verb::Prim(p) => {
1613                // Scalar verbs have cell rank 0: the cells are the elements,
1614                // so the whole buffer is one elementwise pass.
1615                if let MonadOp::Scalar(op) = p.monad {
1616                    return scalar_monad(op, y, ctx.cfg, span);
1617                }
1618                // A MIXED SIMPLE array is already simple, so opening it
1619                // changes nothing — and its cells could not be framed back
1620                // into one array if the rank machinery took them apart.
1621                if p.monad == MonadOp::Open && is_mixed_simple(y) {
1622                    return Ok(y.clone());
1623                }
1624                let frame_rank = y.rank() - effective_rank(p.ranks[0], y.rank());
1625                if frame_rank == 0 {
1626                    return monad_op(p, y, ctx, span);
1627                }
1628                let frame = y.shape[..frame_rank].to_vec();
1629                let n: usize = frame.iter().product();
1630                if n == 0 {
1631                    let cell = fill_cell(y, frame_rank, self.is_pure());
1632                    return Ok(empty_frame(&frame, y.dtype(), cell, ctx, |cell, c| {
1633                        monad_op(p, cell, c, span)
1634                    }));
1635                }
1636                let cells = each_cell(n, y.count(), self.is_pure(), ctx, |i, c| {
1637                    monad_op(p, &y.cell_at(frame_rank, i), c, span)
1638                })?;
1639                assemble(&frame, cells, span)
1640            }
1641            Verb::Rank(v, r) => {
1642                let frame_rank = y.rank() - effective_rank(r[0], y.rank());
1643                if frame_rank == 0 {
1644                    // The inner verb applies its own rank machinery to the
1645                    // whole argument; that is what `"` means.
1646                    return v.monad(y, ctx, span);
1647                }
1648                // A reduction over vector cells is every row of the buffer
1649                // folded in place, without an array per cell.
1650                if let Some(a) = reduce_vector_cells(v, y, frame_rank) {
1651                    return Ok(a);
1652                }
1653                let frame = y.shape[..frame_rank].to_vec();
1654                let n: usize = frame.iter().product();
1655                if n == 0 {
1656                    let cell = fill_cell(y, frame_rank, self.is_pure());
1657                    return Ok(empty_frame(&frame, y.dtype(), cell, ctx, |cell, c| {
1658                        v.monad(cell, c, span)
1659                    }));
1660                }
1661                let cells = each_cell(n, y.count(), self.is_pure(), ctx, |i, c| {
1662                    v.monad(&y.cell_at(frame_rank, i), c, span)
1663                })?;
1664                assemble(&frame, cells, span)
1665            }
1666            Verb::Reduce(v) | Verb::NWise(v) => reduce(v, y, ctx, span),
1667            Verb::Windowed(v, kind) => {
1668                runs(v, y, *kind == WindowKind::Suffix, ctx, span)
1669            }
1670            Verb::Commute(v) => v.dyad(y, y, ctx, span),
1671            Verb::PowerN(v, p) => power(v, p.clone(), None, y, ctx, span),
1672            Verb::Fork(f, g, h) => {
1673                let l = f.monad(y, ctx, span)?;
1674                let r = h.monad(y, ctx, span)?;
1675                g.dyad(&l, &r, ctx, span)
1676            }
1677            Verb::NounFork(n, g, h) => {
1678                let r = h.monad(y, ctx, span)?;
1679                g.dyad(n, &r, ctx, span)
1680            }
1681            Verb::Hook(f, g) => {
1682                let r = g.monad(y, ctx, span)?;
1683                f.dyad(y, &r, ctx, span)
1684            }
1685            Verb::Atop(f, g) | Verb::Compose(f, g) => {
1686                let r = g.monad(y, ctx, span)?;
1687                f.monad(&r, ctx, span)
1688            }
1689            Verb::BondLeft(m, v) => v.dyad(m, y, ctx, span),
1690            Verb::BondRight(v, n) => v.dyad(y, n, ctx, span),
1691            Verb::Each(u, rule) => {
1692                let n = y.count();
1693                let cells = each_cell(n, n, self.is_pure(), ctx, |i, c| {
1694                    let opened = open_cell(&atom(y, i));
1695                    Ok(enclose(&u.monad(&opened, c, span)?, *rule))
1696                })?;
1697                assemble(&y.shape, cells, span)
1698            }
1699            // `u&., y`: the shape is put back afterwards, so a ravel that
1700            // says nothing about where it came from still has an inverse
1701            // for as long as this one argument is in hand.
1702            Verb::UnderRavel(u) => {
1703                let flat = Array::new(vec![y.count()], y.data.clone());
1704                let r = u.monad(&flat, ctx, span)?;
1705                let shape = Array::from_i64(y.shape.iter().map(|&n| n as i64).collect());
1706                reshape(&shape, &r, false, false, ctx.cfg.near(), span)
1707            }
1708            Verb::Fit(v, n) => {
1709                let tol = Tol { ct: *n, ..ctx.cfg.tol };
1710                ctx.with_tol(tol, |c| v.monad(y, c, span))
1711            }
1712            // `m} y` with one index is J's item selection.
1713            Verb::Amend(m) => {
1714                if m.rank() != 0 || y.rank() > 1 {
1715                    return Err(Error::new(
1716                        ErrorKind::Rank,
1717                        "selecting with m} takes one index into a list",
1718                        Some(span),
1719                    ));
1720                }
1721                from_index(m, y, ctx.cfg.near(), span)
1722            }
1723            // `u} y` computes the indices first: it is `(u y)} y`.
1724            Verb::AmendVerb(u) => {
1725                let m = u.monad(y, ctx, span)?;
1726                Verb::Amend(m).monad(y, ctx, span)
1727            }
1728            // The monad shifts by one, the fill taking the place the
1729            // first item left: `|.!.f y` is `_1 |.!.f y`.
1730            Verb::ShiftFill(fill) => {
1731                shift_fill(&Array::scalar_i64(-1), y, fill, ctx.cfg.near(), span)
1732            }
1733            Verb::Memo(u, cache) => memoised(u, cache, None, y, ctx, span),
1734            Verb::Characteristics(u) => characteristics(u, y, span),
1735            Verb::Before(f, g) => {
1736                let l = f.monad(y, ctx, span)?;
1737                g.dyad(&l, y, ctx, span)
1738            }
1739            Verb::KeyPairs(u) => key_pairs(u, y, None, ctx, span),
1740            Verb::UserDerived { def, alpha, omega } => {
1741                let body = def.pick(alpha, omega.as_ref())?.clone();
1742                with_operands(alpha, omega.as_ref(), ctx, |c| body.monad(y, c, span))
1743            }
1744            Verb::Level { u, level, spread } => {
1745                at_level(u, *level, *spread, y, ctx, span)
1746            }
1747            Verb::Key(u) => oblique(u, y, ctx, span),
1748            Verb::Cut(u, n) => cut(u, None, y, *n, ctx, span),
1749            Verb::PowerV(u, v) => power_v(u, v, None, y, ctx, span),
1750            Verb::PowerUntil(u, v) => power_until(u, v, y, ctx, span),
1751            Verb::AlongAxis(u, k) => along_axis(u, None, y, *k, ctx, span),
1752            Verb::Explicit(d) => crate::ir::call_explicit(d, None, y, ctx, span),
1753            Verb::SelfRef => {
1754                let d = self_ref(ctx, span)?;
1755                crate::ir::call_explicit(&d, None, y, ctx, span)
1756            }
1757            Verb::Named(n) => named_verb(ctx, n, span)?.monad(y, ctx, span),
1758            Verb::WithObverse(v, _) => v.monad(y, ctx, span),
1759            Verb::Adverse(v, w) => match v.monad(y, ctx, span) {
1760                Err(e) if e.kind != ErrorKind::NotYet => w.monad(y, ctx, span),
1761                other => other,
1762            },
1763            Verb::Beside(f, g) => {
1764                let r = g.monad(y, ctx, span)?;
1765                f.monad(&r, ctx, span)
1766            }
1767            Verb::Hypergeometric { num, den } => hypergeometric(num, den, y, span),
1768            Verb::Agenda(vs, w) => {
1769                agenda_pick(vs, w, None, y, ctx, span)?.monad(y, ctx, span)
1770            }
1771            Verb::Evoke(vs, n) => evoke(vs, *n, None, y, ctx, span),
1772            Verb::Stencil(u, w) => stencil(u, w, y, ctx, span),
1773            Verb::InnerProduct { u, v, apl } => determinant(u, v, *apl, y, ctx, span),
1774        }
1775    }
1776
1777    /// Monadic application to a column-major argument, for the verbs that
1778    /// read one where it lies. None means this verb is not one of them and
1779    /// the caller must materialise the rows first.
1780    ///
1781    /// Every arm here either reads the buffer in an order it chooses (the
1782    /// folds), reads it elementwise (order cannot matter), or answers from
1783    /// the shape alone. Nothing else may be added without the same argument
1784    /// holding for it.
1785    fn monad_columns(&self, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Option<Result<Array>> {
1786        debug_assert!(!y.is_row_major());
1787        match self {
1788            Verb::Prim(p) => match p.monad {
1789                // Elementwise: every element is read and written where it
1790                // lies, so the answer carries the argument's own layout.
1791                MonadOp::Scalar(op) => Some(scalar_monad(op, y, ctx.cfg, span)),
1792                // The shape is the logical one whatever the buffer does.
1793                MonadOp::ShapeOf | MonadOp::Tally => Some(monad_op(p, y, ctx, span)),
1794                // Reversing the axes of a column-major buffer is reading the
1795                // same buffer as a row-major one of the reversed shape: the
1796                // transpose that costs nothing.
1797                MonadOp::TransposeAxes => Some(Ok(transpose_axes(y))),
1798                _ => None,
1799            },
1800            // `u/ y` folds the leading axis, and in this layout the leading
1801            // axis is what each contiguous run holds.
1802            Verb::Reduce(v) | Verb::NWise(v) => reduce_columns(v, y).map(Ok),
1803            // `u/"1 y` folds each row across the columns, which is one
1804            // elementwise pass per column and no transpose at all.
1805            Verb::Rank(v, r) => {
1806                if y.rank() != effective_rank(r[0], y.rank()) + 1 {
1807                    return None;
1808                }
1809                reduce_rows_columns(v, y).map(Ok)
1810            }
1811            _ => None,
1812        }
1813    }
1814
1815    /// Full dyadic application including rank/frame/agreement machinery.
1816    ///
1817    /// The other place a column-major argument is dealt with: an
1818    /// elementwise verb over arguments that agree exactly reads the buffers
1819    /// as they lie and keeps the layout, and everything else is given rows.
1820    pub fn dyad(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1821        let _depth = Nesting::enter(span)?;
1822        // As in `monad`, the application starts out not shy.
1823        ctx.shy = false;
1824        // As in `monad`: only `x $. y` reads a sparse argument as it lies,
1825        // and even there the left one names a form and is always dense.
1826        let (dense_x, dense_y);
1827        let x = if x.is_sparse() {
1828            dense_x = x.densified();
1829            &dense_x
1830        } else {
1831            x
1832        };
1833        let y = if y.is_sparse() && !self.dyad_reads_sparse() {
1834            dense_y = y.densified();
1835            &dense_y
1836        } else {
1837            y
1838        };
1839        if x.is_row_major() && y.is_row_major() {
1840            return self.dyad_rows(x, y, ctx, span);
1841        }
1842        if let Some(layout) = self.elementwise_layout(x, y) {
1843            return Ok(self.dyad_rows(x, y, ctx, span)?.with_layout(layout));
1844        }
1845        self.dyad_rows(&x.to_row_major(), &y.to_row_major(), ctx, span)
1846    }
1847
1848    /// The layout a dyadic result keeps when its arguments are not both
1849    /// row-major: an elementwise primitive over a scalar and an array, or
1850    /// over two arrays of one shape and one layout, computes each element
1851    /// from the elements at its own index and nothing else.
1852    fn elementwise_layout(&self, x: &Array, y: &Array) -> Option<Layout> {
1853        let Verb::Prim(p) = self else { return None };
1854        if !matches!(p.dyad, DyadOp::Scalar(_)) {
1855            return None;
1856        }
1857        if x.rank() == 0 {
1858            return Some(y.layout());
1859        }
1860        if y.rank() == 0 {
1861            return Some(x.layout());
1862        }
1863        (x.shape == y.shape && x.layout() == y.layout()).then(|| x.layout())
1864    }
1865
1866    /// Dyadic application proper: reached with row-major arguments, or with
1867    /// arguments whose layout the verb above has established it is
1868    /// indifferent to.
1869    fn dyad_rows(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1870        match self {
1871            Verb::Prim(_) | Verb::Rank(_, _) | Verb::Each(..) => {
1872                self.dyad_ranked(x, y, ctx, span)
1873            }
1874            // `x u\ y` and `x u\. y` need the frame machinery: their left
1875            // cell is an atom.
1876            Verb::Windowed(_, WindowKind::Prefix | WindowKind::Suffix) => {
1877                self.dyad_ranked(x, y, ctx, span)
1878            }
1879            Verb::Windowed(_, WindowKind::Scan) => {
1880                Err(Error::not_yet("dyadic scan (x f\\ y)", span))
1881            }
1882            Verb::Commute(v) => v.dyad(y, x, ctx, span),
1883            Verb::PowerN(v, p) => power(v, p.clone(), Some(x), y, ctx, span),
1884            // `x u/ y` is the table: every cell of x against every cell of y.
1885            Verb::Reduce(v) => table(v, x, y, ctx, span),
1886            // `n f/ y` is APL's n-wise reduction, a different function.
1887            Verb::NWise(v) => nwise(v, x, y, ctx, span),
1888            Verb::Fork(f, g, h) => {
1889                let l = f.dyad(x, y, ctx, span)?;
1890                let r = h.dyad(x, y, ctx, span)?;
1891                g.dyad(&l, &r, ctx, span)
1892            }
1893            Verb::NounFork(n, g, h) => {
1894                let r = h.dyad(x, y, ctx, span)?;
1895                g.dyad(n, &r, ctx, span)
1896            }
1897            Verb::Hook(f, g) => {
1898                let r = g.monad(y, ctx, span)?;
1899                f.dyad(x, &r, ctx, span)
1900            }
1901            Verb::Atop(f, g) => {
1902                let r = g.dyad(x, y, ctx, span)?;
1903                f.monad(&r, ctx, span)
1904            }
1905            Verb::Compose(f, g) => {
1906                let l = g.monad(x, ctx, span)?;
1907                let r = g.monad(y, ctx, span)?;
1908                f.dyad(&l, &r, ctx, span)
1909            }
1910            Verb::Fit(v, n) => {
1911                let tol = Tol { ct: *n, ..ctx.cfg.tol };
1912                ctx.with_tol(tol, |c| v.dyad(x, y, c, span))
1913            }
1914            Verb::Amend(m) => amend(m, x, y, ctx.cfg.near(), span),
1915            // `x u} y` is `x (x u y)} y`: u names the places to amend.
1916            Verb::AmendVerb(u) => {
1917                let m = u.dyad(x, y, ctx, span)?;
1918                amend(&m, x, y, ctx.cfg.near(), span)
1919            }
1920            Verb::ShiftFill(fill) => shift_fill(x, y, fill, ctx.cfg.near(), span),
1921            Verb::Memo(u, cache) => memoised(u, cache, Some(x), y, ctx, span),
1922            Verb::Characteristics(_) => {
1923                Err(Error::domain("u b. has no dyadic meaning", span))
1924            }
1925            Verb::Before(f, g) => {
1926                let l = f.monad(x, ctx, span)?;
1927                g.dyad(&l, y, ctx, span)
1928            }
1929            Verb::KeyPairs(u) => key_pairs(u, x, Some(y), ctx, span),
1930            Verb::UserDerived { def, alpha, omega } => {
1931                let body = def.pick(alpha, omega.as_ref())?.clone();
1932                with_operands(alpha, omega.as_ref(), ctx, |c| body.dyad(x, y, c, span))
1933            }
1934            Verb::Level { u, level, spread } => {
1935                at_level_dyad(u, *level, *spread, x, y, ctx, span)
1936            }
1937            Verb::Key(u) => key(u, x, y, ctx, span),
1938            Verb::Cut(u, n) => cut(u, Some(x), y, *n, ctx, span),
1939            Verb::PowerV(u, v) => power_v(u, v, Some(x), y, ctx, span),
1940            Verb::PowerUntil(..) => {
1941                Err(Error::not_yet("dyadic power with a function operand (x f⍣g y)", span))
1942            }
1943            Verb::AlongAxis(u, k) => along_axis(u, Some(x), y, *k, ctx, span),
1944            Verb::Explicit(d) => crate::ir::call_explicit(d, Some(x), y, ctx, span),
1945            Verb::SelfRef => {
1946                let d = self_ref(ctx, span)?;
1947                crate::ir::call_explicit(&d, Some(x), y, ctx, span)
1948            }
1949            Verb::Named(n) => named_verb(ctx, n, span)?.dyad(x, y, ctx, span),
1950            Verb::WithObverse(v, _) => v.dyad(x, y, ctx, span),
1951            Verb::Adverse(v, w) => match v.dyad(x, y, ctx, span) {
1952                Err(e) if e.kind != ErrorKind::NotYet => w.dyad(x, y, ctx, span),
1953                other => other,
1954            },
1955            Verb::Beside(f, g) => {
1956                let r = g.monad(y, ctx, span)?;
1957                f.dyad(x, &r, ctx, span)
1958            }
1959            Verb::Hypergeometric { .. } => {
1960                Err(Error::domain("m H. n has no dyadic meaning", span))
1961            }
1962            Verb::Agenda(vs, w) => {
1963                agenda_pick(vs, w, Some(x), y, ctx, span)?.dyad(x, y, ctx, span)
1964            }
1965            Verb::Evoke(vs, n) => evoke(vs, *n, Some(x), y, ctx, span),
1966            Verb::InnerProduct { u, v, apl } => inner_product(u, v, *apl, x, y, ctx, span),
1967            Verb::Stencil(..) => {
1968                Err(Error::domain("f⌺w has no dyadic meaning", span))
1969            }
1970            // J gives a bond, and under-ravel, one valence only.
1971            Verb::BondLeft(..) | Verb::BondRight(..) | Verb::UnderRavel(_) => {
1972                Err(Error::domain(format!("{} has no dyadic meaning", self.name()), span))
1973            }
1974        }
1975    }
1976
1977    /// Dyadic application for the verbs that carry cell ranks.
1978    fn dyad_ranked(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
1979        let ranks = self.ranks();
1980        let er_l = effective_rank(ranks[1], x.rank());
1981        let er_r = effective_rank(ranks[2], y.rank());
1982        if er_l == 0 && er_r == 0 {
1983            // Both cells are elements: run the flat elementwise path instead
1984            // of materialising one Array per element.
1985            if let Some(op) = self.scalar_dyad_op() {
1986                return scalar_dyad(op, x, y, ctx.cfg, span);
1987            }
1988        }
1989        let fxl = x.rank() - er_l;
1990        let fyl = y.rank() - er_r;
1991        let p = agree(&x.shape[..fxl], &y.shape[..fyl], &x.shape, &y.shape, ctx.cfg.agreement, span)?;
1992        if p.frame.is_empty() {
1993            return self.dyad_cell(x, y, ctx, span);
1994        }
1995        if p.n == 0 {
1996            let right = fill_cell(y, fyl, self.is_pure());
1997            let cell = fill_cell(x, fxl, self.is_pure()).filter(|_| right.is_some());
1998            return Ok(empty_frame(&p.frame, y.dtype(), cell, ctx, |left, c| {
1999                let right = right.as_ref().expect("a left fill cell comes with a right one");
2000                self.dyad_cell(left, right, c, span)
2001            }));
2002        }
2003        let work = x.count().max(y.count());
2004        let cells = each_cell(p.n, work, self.is_pure(), ctx, |i, c| {
2005            let xc = x.cell_at(fxl, i / p.x_div);
2006            let yc = y.cell_at(fyl, i / p.y_div);
2007            self.dyad_cell(&xc, &yc, c, span)
2008        })?;
2009        assemble(&p.frame, cells, span)
2010    }
2011
2012    /// The meaning applied to one pair of cells by `dyad_ranked`.
2013    fn dyad_cell(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
2014        match self {
2015            // The one dyad that writes: it needs the sink, and the
2016            // dispatcher below it is the pure half of the evaluator.
2017            Verb::Prim(p) if p.dyad == DyadOp::WriteStream => {
2018                stream_number(y, 2, "1!:2 writes", span)?;
2019                (ctx.out)(&format!("{}\n", crate::fmt::format_array(x, &ctx.cfg.fmt)));
2020                Ok(x.clone())
2021            }
2022            Verb::Prim(p) => dyad_op(p, x, y, ctx.cfg, span),
2023            Verb::Rank(v, _) => v.dyad(x, y, ctx, span),
2024            // The infix takes runs of x items; the outfix leaves them out.
2025            Verb::Windowed(v, WindowKind::Suffix) => outfix(v, x, y, ctx, span),
2026            Verb::Windowed(v, _) => infix(v, x, y, ctx, span),
2027            Verb::Each(u, rule) => {
2028                let r = u.dyad(&open_cell(x), &open_cell(y), ctx, span)?;
2029                Ok(enclose(&r, *rule))
2030            }
2031            _ => Err(Error::internal("dyad_cell on a verb without cell ranks")),
2032        }
2033    }
2034
2035    /// The elementwise dyadic operation this verb performs on element cells,
2036    /// if it performs one.
2037    fn scalar_dyad_op(&self) -> Option<ScalarDyad> {
2038        match self {
2039            Verb::Prim(p) => match p.dyad {
2040                DyadOp::Scalar(op) => Some(op),
2041                _ => None,
2042            },
2043            Verb::Rank(v, _) => v.scalar_dyad_op(),
2044            _ => None,
2045        }
2046    }
2047}
2048
2049/// Effective cell rank: nonnegative rank clamps to the argument's rank;
2050/// negative rank means "leave |r| frame axes" (at least rank 0 cells).
2051pub fn effective_rank(r: i64, arg_rank: usize) -> usize {
2052    if r >= 0 {
2053        (r as usize).min(arg_rank)
2054    } else {
2055        arg_rank.saturating_sub(r.unsigned_abs() as usize)
2056    }
2057}
2058
2059/// Apply `f` to the `n` cells of a frame, in index order.
2060///
2061/// Cells are independent, so a pure verb runs them on several threads and
2062/// the results are framed afterwards; an impure one keeps the caller's
2063/// context, and with it the order its output appears in. `work` is the
2064/// number of elements the whole application touches, which decides whether
2065/// splitting is worth it. Either way the first failing cell in index order
2066/// supplies the error.
2067/// The definition `$:` or `∇` names: the innermost one now running.
2068fn self_ref(ctx: &Ctx<'_>, span: Span) -> Result<Arc<crate::ir::ExplicitDef>> {
2069    ctx.env.current_def().ok_or_else(|| {
2070        Error::new(
2071            ErrorKind::Value,
2072            "self-reference outside an explicit definition",
2073            Some(span),
2074        )
2075    })
2076}
2077
2078/// A verb the program named earlier, resolved when it is applied.
2079fn named_verb(ctx: &Ctx<'_>, name: &str, span: Span) -> Result<Verb> {
2080    ctx.env.verb(name).cloned().ok_or_else(|| {
2081        Error::new(ErrorKind::Value, format!("undefined verb: {name}"), Some(span))
2082    })
2083}
2084
2085fn each_cell<F>(
2086    n: usize,
2087    work: usize,
2088    pure: bool,
2089    ctx: &mut Ctx<'_>,
2090    f: F,
2091) -> Result<Vec<Array>>
2092where
2093    F: Fn(usize, &mut Ctx<'_>) -> Result<Array> + Sync + Send,
2094{
2095    if pure && n > 1 && par::worth_it(work) {
2096        let cfg = ctx.cfg;
2097        return par::map_indexed(n, |i| cfg.pure(|c| f(i, c))).into_iter().collect();
2098    }
2099    (0..n).map(|i| f(i, ctx)).collect()
2100}
2101
2102// ---------------------------------------------------------------- naming
2103
2104fn one_rank(r: i64) -> String {
2105    if r == RANK_INF { "_".to_string() } else { r.to_string() }
2106}
2107
2108/// The rank list as `"` writes it: one number when all three agree,
2109/// otherwise monadic, dyadic-left, dyadic-right.
2110fn rank_str(r: [i64; 3]) -> String {
2111    if r[0] == r[1] && r[1] == r[2] {
2112        one_rank(r[0])
2113    } else {
2114        format!("{} {} {}", one_rank(r[0]), one_rank(r[1]), one_rank(r[2]))
2115    }
2116}
2117
2118/// A shape as it appears in diagnostics.
2119fn show_shape(shape: &[usize]) -> String {
2120    if shape.is_empty() {
2121        return "(scalar)".to_string();
2122    }
2123    shape.iter().map(|n| n.to_string()).collect::<Vec<_>>().join(" ")
2124}
2125
2126// ------------------------------------------------------------- indexing
2127
2128/// Row-major strides for `shape`.
2129fn strides(shape: &[usize]) -> Vec<usize> {
2130    let mut s = vec![1usize; shape.len()];
2131    for k in (0..shape.len().saturating_sub(1)).rev() {
2132        s[k] = s[k + 1] * shape[k + 1];
2133    }
2134    s
2135}
2136
2137/// Step `coord` to the next position in row-major order within `shape`.
2138fn odometer(coord: &mut [usize], shape: &[usize]) {
2139    for k in (0..coord.len()).rev() {
2140        coord[k] += 1;
2141        if coord[k] < shape[k] {
2142            return;
2143        }
2144        coord[k] = 0;
2145    }
2146}
2147
2148/// Append element `i` of `src` to `dst`. Both must have the same dtype.
2149fn push_elem(dst: &mut Data, src: &Data, i: usize) {
2150    match (dst, src) {
2151        (Data::Bool(a), Data::Bool(b)) => a.push(b[i]),
2152        (Data::I64(a), Data::I64(b)) => a.push(b[i]),
2153        (Data::Ext(a), Data::Ext(b)) => a.push(b[i].clone()),
2154        (Data::Rat(a), Data::Rat(b)) => a.push(b[i].clone()),
2155        (Data::F64(a), Data::F64(b)) => a.push(b[i]),
2156        (Data::Complex(a), Data::Complex(b)) => a.push(b[i]),
2157        (Data::Char(a), Data::Char(b)) => a.push(b[i]),
2158        (Data::Symbol(a), Data::Symbol(b)) => a.push(b[i]),
2159        (Data::Box(a), Data::Box(b)) => a.push(b[i].clone()),
2160        _ => debug_assert!(false, "push_elem across dtypes"),
2161    }
2162}
2163
2164/// `n` fill elements of the given type.
2165fn fill_data(dtype: DType, n: usize) -> Data {
2166    let mut d = Data::empty(dtype);
2167    for _ in 0..n {
2168        d.push_fill();
2169    }
2170    d
2171}
2172
2173/// The largest fill cell worth building to learn a shape from.
2174const FILL_CELL_LIMIT: usize = 1 << 20;
2175
2176/// A cell to learn a shape from where the application had none to run.
2177///
2178/// An argument whose own frame is not empty still HAS cells — a dyad frames
2179/// over both arguments, and only one of them need be the empty one — so its
2180/// first cell stands in as it is, and only an argument with no cells at all
2181/// is stood in for by a cell of fills.
2182///
2183/// `None` where the verb is not pure — running it to learn a shape would be
2184/// running it for its effects — or where the cell is too large to be worth
2185/// building.
2186fn fill_cell(y: &Array, frame_rank: usize, pure: bool) -> Option<Array> {
2187    if !pure {
2188        return None;
2189    }
2190    if y.shape[..frame_rank].iter().all(|&d| d != 0) {
2191        return Some(y.cell_at(frame_rank, 0));
2192    }
2193    let shape = y.shape[frame_rank..].to_vec();
2194    let n: usize = shape.iter().product();
2195    if n > FILL_CELL_LIMIT {
2196        return None;
2197    }
2198    // A nested argument that remembers its items fills with the prototype,
2199    // so that mixing an empty keeps the axes its items had.
2200    let fill = y.proto().cloned();
2201    let mut data = Data::empty(y.dtype());
2202    for _ in 0..n {
2203        push_gap(&mut data, &fill);
2204    }
2205    Some(Array::new(shape, data))
2206}
2207
2208/// The result of a cell-by-cell application that has no cells to frame.
2209///
2210/// The frame says how many cells there would have been, not what shape one
2211/// would have had, so an empty of the frame's shape alone drops whatever
2212/// axes the cells carried: `(,"1) i. 0 3` is a 0 by 3 table, not a list.
2213/// The missing axes come from running the verb once on a cell of fills and
2214/// keeping the shape of the answer, which is J's own rule. A verb that
2215/// refuses the fill cell, or a cell there was no point building, leaves the
2216/// frame standing on its own, holding the argument's type.
2217fn empty_frame(
2218    frame: &[usize],
2219    dtype: DType,
2220    cell: Option<Array>,
2221    ctx: &mut Ctx<'_>,
2222    run: impl FnOnce(&Array, &mut Ctx<'_>) -> Result<Array>,
2223) -> Array {
2224    let mut shape = frame.to_vec();
2225    if let Some(cell) = cell
2226        && let Ok(answer) = run(&cell, ctx)
2227    {
2228        shape.extend_from_slice(&answer.shape);
2229        return Array::new(shape, Data::empty(answer.dtype()));
2230    }
2231    Array::new(shape, Data::empty(dtype))
2232}
2233
2234// ------------------------------------------------------------ agreement
2235
2236/// How result cells map back to argument cells: result cell `i` uses left
2237/// cell `i / x_div` and right cell `i / y_div`.
2238struct Pairing {
2239    frame: Vec<usize>,
2240    n: usize,
2241    x_div: usize,
2242    y_div: usize,
2243}
2244
2245fn frame_mismatch(
2246    xs: &[usize],
2247    ys: &[usize],
2248    fx: &[usize],
2249    fy: &[usize],
2250    axis: usize,
2251    span: Span,
2252) -> Error {
2253    // 1-D against 1-D is a length error in both languages; anything else is
2254    // reported as a shape error.
2255    let kind = if fx.len() == 1 && fy.len() == 1 { ErrorKind::Length } else { ErrorKind::Shape };
2256    let note = if axis < fx.len() && axis < fy.len() {
2257        format!("frames first differ at axis {axis}: {} vs {}", fx[axis], fy[axis])
2258    } else {
2259        format!(
2260            "frames have different numbers of axes: {} vs {}, diverging at axis {axis}",
2261            fx.len(),
2262            fy.len()
2263        )
2264    };
2265    Error::new(
2266        kind,
2267        format!(
2268            "arguments do not agree: left shape {}, right shape {}",
2269            show_shape(xs),
2270            show_shape(ys)
2271        ),
2272        Some(span),
2273    )
2274    .note(note)
2275}
2276
2277/// Check frame agreement and build the cell pairing. `xs`/`ys` are the full
2278/// argument shapes, used only for diagnostics.
2279fn agree(
2280    fx: &[usize],
2281    fy: &[usize],
2282    xs: &[usize],
2283    ys: &[usize],
2284    mode: Agreement,
2285    span: Span,
2286) -> Result<Pairing> {
2287    let common = fx.len().min(fy.len());
2288    match mode {
2289        Agreement::LeadingPrefix => {
2290            for i in 0..common {
2291                if fx[i] != fy[i] {
2292                    return Err(frame_mismatch(xs, ys, fx, fy, i, span));
2293                }
2294            }
2295            let (long, short) = if fx.len() >= fy.len() { (fx, fy) } else { (fy, fx) };
2296            let n: usize = long.iter().product();
2297            let surplus: usize = long[short.len()..].iter().product();
2298            let (x_div, y_div) =
2299                if fx.len() >= fy.len() { (1, surplus.max(1)) } else { (surplus.max(1), 1) };
2300            Ok(Pairing { frame: long.to_vec(), n, x_div, y_div })
2301        }
2302        Agreement::ExactOrScalar => {
2303            if fx == fy {
2304                let n: usize = fx.iter().product();
2305                return Ok(Pairing { frame: fx.to_vec(), n, x_div: 1, y_div: 1 });
2306            }
2307            // APL extends any frame of ONE cell, whatever its rank, not
2308            // only a scalar one: `(1 1⍴5)+1 2 3` is `6 7 8`. A rank-0 frame
2309            // — a true scalar — always gives way to the other side, and
2310            // between two one-cell frames that are not scalars the answer
2311            // keeps the RIGHT one: `(1 1⍴5)+,3` is a one-item VECTOR, while
2312            // `(1 1⍴5)+3` keeps the 1 by 1 table.
2313            let one = |f: &[usize]| f.iter().product::<usize>() == 1;
2314            if fx.is_empty() || (one(fx) && !fy.is_empty()) {
2315                let n: usize = fy.iter().product();
2316                return Ok(Pairing { frame: fy.to_vec(), n, x_div: n.max(1), y_div: 1 });
2317            }
2318            if fy.is_empty() || one(fy) {
2319                let n: usize = fx.iter().product();
2320                return Ok(Pairing { frame: fx.to_vec(), n, x_div: 1, y_div: n.max(1) });
2321            }
2322            let axis = (0..common).find(|&i| fx[i] != fy[i]).unwrap_or(common);
2323            Err(frame_mismatch(xs, ys, fx, fy, axis, span))
2324        }
2325    }
2326}
2327
2328// ------------------------------------------------------------- assembly
2329
2330/// Frame results that need not share a depth, which is how APL collects the
2331/// values of an application between items: `,\1 2 3` puts the simple scalar
2332/// `1` beside two enclosed vectors. A simple scalar cannot be nested, so it
2333/// is enclosed here to take its place among the others; anything already
2334/// alike goes straight to [`assemble`]. J refuses such a mixture instead,
2335/// and reaches [`assemble`] directly.
2336fn assemble_items(frame: &[usize], mut cells: Vec<Array>, span: Span) -> Result<Array> {
2337    let boxes = cells.iter().filter(|c| c.dtype() == DType::Box).count();
2338    if boxes > 0 && boxes < cells.len() {
2339        for c in &mut cells {
2340            if c.dtype() != DType::Box {
2341                *c = boxed_elements(c);
2342            }
2343        }
2344    }
2345    assemble(frame, cells, span)
2346}
2347
2348/// The same array with every element held as its own value, so that it can
2349/// be framed beside cells whose elements are nested.
2350fn boxed_elements(a: &Array) -> Array {
2351    let row = a.to_row_major();
2352    let held: Vec<Array> = (0..row.count()).map(|i| atom(&row, i)).collect();
2353    Array::new(row.shape.clone(), Data::Box(held.into()))
2354}
2355
2356/// Frame the results of a cell-by-cell application into one array.
2357///
2358/// The cells arrive as their verb left them, and a verb may leave a
2359/// column-major one — `|:` flips the layout flag rather than moving the
2360/// buffer. Framing splices the buffers end to end, so every cell is made
2361/// row-major first; an already row-major one costs a refcount bump.
2362fn assemble(frame: &[usize], cells: Vec<Array>, span: Span) -> Result<Array> {
2363    if cells.is_empty() {
2364        // Nothing to take a cell shape from. J runs the verb on a fill cell
2365        // to learn the shape; we yield an empty array of the frame's shape.
2366        return Ok(Array::new(frame.to_vec(), Data::empty(DType::I64)));
2367    }
2368    let cells: Vec<Array> =
2369        if cells.iter().all(Array::is_row_major) {
2370            cells
2371        } else {
2372            cells.iter().map(Array::to_row_major).collect()
2373        };
2374    // A cell with no elements takes the type of the cells that have some,
2375    // rather than clashing with them: `(0$'a') ,: 1 2 3` frames an empty
2376    // character list beside a numeric one and answers two numeric rows.
2377    // Where every cell is empty the wider container wins — a box over a
2378    // character, a character over a number.
2379    let mut dt = cells.iter().find(|c| c.count() > 0).unwrap_or(&cells[0]).dtype();
2380    for c in &cells {
2381        if c.count() == 0 {
2382            continue;
2383        }
2384        dt = DType::promote(dt, c.dtype()).ok_or_else(|| {
2385            let boxed = dt == DType::Box || c.dtype() == DType::Box;
2386            let what = if boxed {
2387                "cannot frame boxed and unboxed results into one array"
2388            } else {
2389                "cannot frame character and numeric results into one array"
2390            };
2391            Error::new(ErrorKind::Type, what, Some(span))
2392        })?;
2393    }
2394    if cells.iter().all(|c| c.count() == 0) {
2395        for c in &cells {
2396            dt = DType::promote(dt, c.dtype()).unwrap_or(match (dt, c.dtype()) {
2397                (DType::Box, _) | (_, DType::Box) => DType::Box,
2398                _ => DType::Char,
2399            });
2400        }
2401    }
2402    let widen = |c: &Array| -> Result<Data> {
2403        if c.count() == 0 {
2404            return Ok(Data::empty(dt));
2405        }
2406        c.data.cast(dt).ok_or_else(|| Error::internal("unsupported widening while framing"))
2407    };
2408
2409    if cells[1..].iter().all(|c| c.shape == cells[0].shape) {
2410        let mut data = Data::empty(dt);
2411        for c in &cells {
2412            if c.dtype() == dt {
2413                data.extend_from(&c.data);
2414            } else {
2415                data.extend_from(&widen(c)?);
2416            }
2417        }
2418        let mut shape = frame.to_vec();
2419        shape.extend_from_slice(&cells[0].shape);
2420        return Ok(Array::new(shape, data));
2421    }
2422
2423    // Unequal cell shapes: pad every cell out to the per-axis maximum,
2424    // aligning lower-rank cells at the trailing axes.
2425    let crank = cells.iter().map(|c| c.rank()).max().unwrap_or(0);
2426    let padded: Vec<Vec<usize>> = cells
2427        .iter()
2428        .map(|c| {
2429            let mut s = vec![1usize; crank - c.rank()];
2430            s.extend_from_slice(&c.shape);
2431            s
2432        })
2433        .collect();
2434    let mut common = vec![0usize; crank];
2435    for s in &padded {
2436        for k in 0..crank {
2437            common[k] = common[k].max(s[k]);
2438        }
2439    }
2440    let cell_n: usize = common.iter().product();
2441    let mut data = Data::empty(dt);
2442    for (c, ps) in cells.iter().zip(&padded) {
2443        let cd = if c.dtype() == dt { c.data.clone() } else { widen(c)? };
2444        let st = strides(ps);
2445        let mut coord = vec![0usize; crank];
2446        for _ in 0..cell_n {
2447            let mut idx = 0usize;
2448            let mut inside = true;
2449            for k in 0..crank {
2450                if coord[k] >= ps[k] {
2451                    inside = false;
2452                    break;
2453                }
2454                idx += coord[k] * st[k];
2455            }
2456            if inside {
2457                push_elem(&mut data, &cd, idx);
2458            } else {
2459                data.push_fill();
2460            }
2461            odometer(&mut coord, &common);
2462        }
2463    }
2464    let mut shape = frame.to_vec();
2465    shape.extend_from_slice(&common);
2466    Ok(Array::new(shape, data))
2467}
2468
2469// ------------------------------------------------------------------ boxes
2470
2471/// Element `i` of `a` as a rank-0 array — the cell an operation of rank 0
2472/// sees.
2473fn atom(a: &Array, i: usize) -> Array {
2474    debug_assert!(a.is_row_major(), "an atom out of a column-major buffer");
2475    Array::new(Vec::new(), a.data.slice(i, i + 1))
2476}
2477
2478/// `< y` / `⊂ y`.
2479fn enclose(y: &Array, rule: Enclose) -> Array {
2480    if rule == Enclose::ExceptSimpleScalar && y.rank() == 0 && y.dtype() != DType::Box {
2481        return y.clone();
2482    }
2483    Array::boxed(y.clone())
2484}
2485
2486/// One rank-0 cell opened: a box gives up its contents, anything else is
2487/// its own contents already.
2488///
2489/// What comes out is row-major. A box is filled with a RESULT, and a result
2490/// carries whatever layout its verb left — `|:&.>` boxes column-major
2491/// matrices — while everything downstream of an open reads a value the way
2492/// a verb's argument is read.
2493pub(crate) fn open_cell(y: &Array) -> Array {
2494    match &y.data {
2495        Data::Box(v) if !v.is_empty() => v[0].to_row_major(),
2496        _ => y.clone(),
2497    }
2498}
2499
2500/// `↑ y` (APL): the first element, disclosed. An empty argument has none,
2501/// so its fill stands in.
2502fn first(y: &Array) -> Array {
2503    if y.count() == 0 {
2504        // A nested empty that remembers its items answers with the
2505        // prototype: `↑0⍴⊂2 3⍴9` is the 2 by 3 table of zeros.
2506        if let Some(p) = y.proto() {
2507            return p.clone();
2508        }
2509        let mut d = Data::empty(y.dtype());
2510        d.push_fill();
2511        return open_cell(&Array::new(Vec::new(), d));
2512    }
2513    open_cell(&atom(y, 0))
2514}
2515
2516/// `≡ y` (APL).
2517fn depth(y: &Array) -> i64 {
2518    match &y.data {
2519        Data::Box(v) => 1 + v.iter().map(depth).max().unwrap_or(0),
2520        _ => i64::from(y.rank() > 0),
2521    }
2522}
2523
2524/// Whether every item of `y`, at every level, has its siblings' depth. A
2525/// simple array is uniform, and so is `1 2∘.⍴3 4`, whose items are of one
2526/// depth and different lengths; `1(2(3 4))` and `(1 2),⊂3 4` are not.
2527fn uniform(y: &Array) -> bool {
2528    let Data::Box(v) = &y.data else { return true };
2529    let Some(head) = v.first() else { return true };
2530    let d = depth(head);
2531    v.iter().all(|b| depth(b) == d && uniform(b))
2532}
2533
2534/// Every leaf array inside `a`, in ravel order.
2535///
2536/// A leaf comes out row-major: a box may hold whatever layout the verb that
2537/// filled it left behind, and a caller that reads the ravel would otherwise
2538/// read a column-major buffer as rows.
2539fn leaves(a: &Array, out: &mut Vec<Array>) {
2540    let a = a.to_row_major();
2541    match &a.data {
2542        Data::Box(v) => {
2543            for b in v.iter() {
2544                leaves(b, out);
2545            }
2546        }
2547        _ => out.push(a),
2548    }
2549}
2550
2551/// `∊ y` (APL): every leaf element as one vector. Leaves that share no one
2552/// type make a MIXED SIMPLE vector, as catenating them would.
2553fn enlist(y: &Array, _span: Span) -> Result<Array> {
2554    let mut parts = Vec::new();
2555    leaves(y, &mut parts);
2556    // An empty leaf contributes no elements, so it does not decide the
2557    // type either.
2558    let mut dt = None;
2559    let mut mixing = false;
2560    for p in parts.iter().filter(|p| p.count() > 0) {
2561        dt = Some(match dt {
2562            None => p.dtype(),
2563            Some(t) => match DType::promote(t, p.dtype()) {
2564                Some(t) => t,
2565                None => {
2566                    mixing = true;
2567                    break;
2568                }
2569            },
2570        });
2571    }
2572    if mixing {
2573        let mut cells: Vec<Array> = Vec::new();
2574        for p in &parts {
2575            let p = p.to_row_major();
2576            cells.extend((0..p.count()).map(|i| atom(&p, i)));
2577        }
2578        return Ok(Array::new(vec![cells.len()], Data::Box(cells.into())));
2579    }
2580    let dt = dt.unwrap_or(DType::I64);
2581    let mut data = Data::empty(dt);
2582    for p in &parts {
2583        let cast = p.data.cast(dt).ok_or_else(|| Error::internal("unsupported widening in enlist"))?;
2584        data.extend_from(&cast);
2585    }
2586    Ok(Array::new(vec![data.len()], data))
2587}
2588
2589/// A scalar repeated over `shape` — how a catenation spreads an atom.
2590fn spread(a: &Array, shape: &[usize]) -> Array {
2591    let n: usize = shape.iter().product();
2592    let mut data = Data::empty(a.dtype());
2593    for _ in 0..n {
2594        push_elem(&mut data, &a.data, 0);
2595    }
2596    Array::new(shape.to_vec(), data)
2597}
2598
2599/// Per-axis maximum of two cell shapes, aligned at their trailing axes —
2600/// the same alignment framing uses.
2601fn wider_shape(a: &[usize], b: &[usize]) -> Vec<usize> {
2602    let r = a.len().max(b.len());
2603    let pad = |s: &[usize]| {
2604        let mut v = vec![1usize; r - s.len()];
2605        v.extend_from_slice(s);
2606        v
2607    };
2608    let (pa, pb) = (pad(a), pad(b));
2609    (0..r).map(|k| pa[k].max(pb[k])).collect()
2610}
2611
2612/// `; y` (J): the items of the opened boxes, one after another. A scalar
2613/// among them spreads over the common item shape, as catenation does; the
2614/// rest are padded with fill, which is what makes raze accept items that
2615/// plain catenation would refuse.
2616fn raze(y: &Array, span: Span) -> Result<Array> {
2617    let opened: Vec<Array> = (0..y.count()).map(|i| open_cell(&atom(y, i))).collect();
2618    let mut common: Option<Vec<usize>> = None;
2619    for a in opened.iter().filter(|a| a.rank() > 0) {
2620        common = Some(match common {
2621            None => a.shape[1..].to_vec(),
2622            Some(c) => wider_shape(&c, &a.shape[1..]),
2623        });
2624    }
2625    let common = common.unwrap_or_default();
2626    let mut cells: Vec<Array> = Vec::new();
2627    for a in &opened {
2628        if a.rank() == 0 {
2629            cells.push(spread(a, &common));
2630            continue;
2631        }
2632        for i in 0..a.items() {
2633            cells.push(a.item(i));
2634        }
2635    }
2636    if cells.is_empty() {
2637        return Ok(Array::new(vec![0], Data::empty(DType::I64)));
2638    }
2639    let n = cells.len();
2640    assemble(&[n], cells, span)
2641}
2642
2643/// `x ; y` (J): x boxed, then y — which joins as it is when it is already
2644/// boxed and boxed when it is not.
2645fn link(x: &Array, y: &Array, span: Span) -> Result<Array> {
2646    let head = Array::boxed(x.clone());
2647    let tail = if y.dtype() == DType::Box { y.clone() } else { Array::boxed(y.clone()) };
2648    catenate(&head, &tail, true, false, span)
2649}
2650
2651/// `a` with every element enclosed, where `other` is boxed and `a` is not.
2652/// The shape is kept, so only the depth changes.
2653fn nest_like(a: &Array, other: &Array) -> Array {
2654    if a.dtype() == DType::Box || other.dtype() != DType::Box {
2655        return a.clone();
2656    }
2657    let cells: Vec<Array> = (0..a.count()).map(|i| atom(a, i)).collect();
2658    Array::new(a.shape.clone(), Data::Box(cells.into()))
2659}
2660
2661/// Every ELEMENT of `a` as a rank-0 box, keeping the shape: APL's mixed
2662/// simple form, which is how libjay holds a value whose elements share no
2663/// one type. Enclosing a simple scalar is no change at all in APL, so the
2664/// form says nothing the value did not already say. An already boxed array
2665/// is left alone.
2666fn spread_scalars(a: &Array) -> Array {
2667    if a.dtype() == DType::Box {
2668        return a.clone();
2669    }
2670    let a = a.to_row_major();
2671    let cells: Vec<Array> = (0..a.count()).map(|i| atom(&a, i)).collect();
2672    Array::new(a.shape.clone(), Data::Box(cells.into()))
2673}
2674
2675/// True where every element of `a` is a simple scalar: the shape libjay
2676/// holds a mixed simple array in, whether or not the types still differ.
2677fn holds_scalar_boxes(a: &Array) -> bool {
2678    match a.as_boxes() {
2679        Some(items) => {
2680            !items.is_empty() && items.iter().all(|b| b.rank() == 0 && b.dtype() != DType::Box)
2681        }
2682        None => false,
2683    }
2684}
2685
2686/// The way back out of [`spread_scalars`]: a boxed array whose every
2687/// element is a simple scalar and where one type covers them all is that
2688/// simple array, and in APL always was. Anything else is returned as it is.
2689///
2690/// This runs over every APL result, which is what keeps the form canonical:
2691/// `2↓1 2,'ab'` is the character vector `ab`, not two boxed characters.
2692fn tightened_mixed(a: Array) -> Array {
2693    let common = match a.as_boxes() {
2694        Some(items) if holds_scalar_boxes(&a) => {
2695            let mut t = items[0].dtype();
2696            let mut ok = true;
2697            for b in &items[1..] {
2698                match DType::promote(t, b.dtype()) {
2699                    Some(next) => t = next,
2700                    None => {
2701                        ok = false;
2702                        break;
2703                    }
2704                }
2705            }
2706            ok.then_some(t)
2707        }
2708        _ => None,
2709    };
2710    let Some(common) = common else { return a };
2711    let mut data = Data::empty(common);
2712    for b in a.as_boxes().expect("checked above") {
2713        match b.data.cast(common) {
2714            Some(widened) => push_elem(&mut data, &widened, 0),
2715            None => return a.clone(),
2716        }
2717    }
2718    Array::new(a.shape.clone(), data)
2719}
2720
2721/// A pair put in one form, where one of them is held as boxed scalars and
2722/// the other is not: the simple one is spread into rank-0 boxes so the two
2723/// compare and join element for element. APL only — J's `<2` is a value of
2724/// its own and never the same as `2`.
2725fn align_mixed(x: &Array, y: &Array, apl: bool) -> (Array, Array) {
2726    if apl && holds_scalar_boxes(x) && y.dtype() != DType::Box {
2727        return (x.clone(), spread_scalars(y));
2728    }
2729    if apl && holds_scalar_boxes(y) && x.dtype() != DType::Box {
2730        return (spread_scalars(x), y.clone());
2731    }
2732    (x.clone(), y.clone())
2733}
2734
2735/// Every item of `y` boxed; an already boxed array is left alone.
2736fn box_items(y: &Array) -> Array {
2737    if y.dtype() == DType::Box {
2738        return y.clone();
2739    }
2740    let n = y.items();
2741    let boxes: Vec<Array> = (0..n).map(|i| item_or_self(y, i)).collect();
2742    Array::new(vec![n], Data::Box(boxes.into()))
2743}
2744
2745/// APL vector notation: `x` becomes one more item in front of the strand
2746/// `y`. Simple scalars stay simple, so `1 2 3` is a plain integer vector
2747/// and only a strand holding something else becomes nested.
2748fn strand(x: &Array, y: &Array, span: Span) -> Result<Array> {
2749    let item = enclose(x, Enclose::ExceptSimpleScalar);
2750    let one = |a: &Array| Array::new(vec![1], a.data.clone());
2751    // A strand of one kind stays a plain array; one that mixes characters
2752    // with numbers becomes APL's MIXED SIMPLE array, which libjay keeps as
2753    // boxed scalars. Its depth is 1 and it displays without borders,
2754    // because a box holding a simple scalar is a scalar in APL.
2755    if item.dtype() != DType::Box
2756        && y.dtype() != DType::Box
2757        && DType::promote(item.dtype(), y.dtype()).is_some()
2758    {
2759        return catenate(&one(&item), y, true, false, span);
2760    }
2761    let head = if item.dtype() == DType::Box { item } else { Array::boxed(item) };
2762    catenate(&one(&head), &box_items(y), true, false, span)
2763}
2764
2765// -------------------------------------------------- elementwise operations
2766
2767fn char_arith(span: Span) -> Error {
2768    Error::new(ErrorKind::Type, "cannot do arithmetic on characters", Some(span))
2769}
2770
2771fn symbol_arith(span: Span) -> Error {
2772    Error::new(
2773        ErrorKind::Type,
2774        "cannot do arithmetic on symbols; `5 s:` gives their names back",
2775        Some(span),
2776    )
2777}
2778
2779fn box_arith(span: Span) -> Error {
2780    Error::new(
2781        ErrorKind::Type,
2782        "cannot do arithmetic on boxed values; open them first (J `>`, APL `⊃`)",
2783        Some(span),
2784    )
2785}
2786
2787/// The complaint an operation makes about an element type it cannot work
2788/// on at all.
2789fn wrong_type(d: DType, span: Span) -> Error {
2790    match d {
2791        DType::Box => box_arith(span),
2792        DType::Symbol => symbol_arith(span),
2793        _ => char_arith(span),
2794    }
2795}
2796
2797/// Borrow numeric data as i64, widening a boolean buffer into `tmp`.
2798///
2799/// The widening is a pass over the whole buffer, so it takes the thread
2800/// pool on the sizes that are worth splitting; the values are the same
2801/// whichever way it runs.
2802fn borrow_i64<'a>(d: &'a Data, tmp: &'a mut Vec<i64>) -> &'a [i64] {
2803    match d {
2804        Data::I64(v) => v,
2805        Data::Bool(v) => {
2806            *tmp = par::map(v, |&b| b as i64);
2807            &tmp[..]
2808        }
2809        // Callers exclude character data before reaching here.
2810        _ => &[],
2811    }
2812}
2813
2814/// Borrow numeric data as f64, widening into `tmp` when needed.
2815fn borrow_f64<'a>(d: &'a Data, tmp: &'a mut Vec<f64>) -> &'a [f64] {
2816    match d {
2817        Data::F64(v) => v,
2818        Data::I64(v) => {
2819            *tmp = par::map(v, |&x| x as f64);
2820            &tmp[..]
2821        }
2822        Data::Bool(v) => {
2823            *tmp = par::map(v, |&x| x as f64);
2824            &tmp[..]
2825        }
2826        Data::Ext(v) => {
2827            *tmp = par::map(v, exact::ext_to_f64);
2828            &tmp[..]
2829        }
2830        Data::Rat(v) => {
2831            *tmp = par::map(v, Rat::to_f64);
2832            &tmp[..]
2833        }
2834        _ => &[],
2835    }
2836}
2837
2838/// Borrow numeric data as complex, widening into `tmp` when needed.
2839fn borrow_cx<'a>(d: &'a Data, tmp: &'a mut Vec<Cx>) -> &'a [Cx] {
2840    match d {
2841        Data::Complex(v) => v,
2842        Data::Ext(v) => {
2843            *tmp = par::map(v, |x| [exact::ext_to_f64(x), 0.0]);
2844            &tmp[..]
2845        }
2846        Data::Rat(v) => {
2847            *tmp = par::map(v, |x| [x.to_f64(), 0.0]);
2848            &tmp[..]
2849        }
2850        Data::F64(v) => {
2851            *tmp = par::map(v, |&x| [x, 0.0]);
2852            &tmp[..]
2853        }
2854        Data::I64(v) => {
2855            *tmp = par::map(v, |&x| [x as f64, 0.0]);
2856            &tmp[..]
2857        }
2858        Data::Bool(v) => {
2859            *tmp = v.iter().map(|&x| [x as f64, 0.0]).collect();
2860            &tmp[..]
2861        }
2862        _ => &[],
2863    }
2864}
2865
2866/// One element of a narrow buffer, read as the type a pass computes in.
2867///
2868/// This is what lets a pass over operands of two different types run
2869/// without a widened copy of either: the promotion happens where the
2870/// element is read, inside the chunk, so the only buffer the pass touches
2871/// besides its arguments is its own result. Promotion and then the
2872/// operation is exactly what the widened copy would have fed it, so the
2873/// answers are identical either way.
2874pub(crate) trait Widen<T>: Copy + Send + Sync {
2875    fn widen(self) -> T;
2876}
2877
2878macro_rules! widens {
2879    ($($from:ty => $to:ty : |$v:ident| $e:expr;)*) => {
2880        $(impl Widen<$to> for $from {
2881            #[inline(always)]
2882            fn widen(self) -> $to {
2883                let $v = self;
2884                $e
2885            }
2886        })*
2887    };
2888}
2889
2890widens! {
2891    u8 => i64: |v| v as i64;
2892    i64 => i64: |v| v;
2893    u8 => f64: |v| v as f64;
2894    i64 => f64: |v| v as f64;
2895    f64 => f64: |v| v;
2896    u8 => Cx: |v| [v as f64, 0.0];
2897    i64 => Cx: |v| [v as f64, 0.0];
2898    f64 => Cx: |v| [v, 0.0];
2899    Cx => Cx: |v| v;
2900}
2901
2902/// Bind `$s` to the buffer behind one numeric operand of an integer pass,
2903/// in the buffer's own element type, and evaluate `$body` with it.
2904macro_rules! i64_source {
2905    ($d:expr, $tmp:ident, $s:ident, $body:expr) => {
2906        match $d {
2907            Data::I64(v) => {
2908                let $s: &[i64] = v;
2909                $body
2910            }
2911            Data::Bool(v) => {
2912                let $s: &[u8] = v;
2913                $body
2914            }
2915            other => {
2916                let $s: &[i64] = borrow_i64(other, &mut $tmp);
2917                $body
2918            }
2919        }
2920    };
2921}
2922
2923/// The same for a float pass. The exact types have no fixed-width buffer to
2924/// read element by element, so they keep the widened copy.
2925macro_rules! f64_source {
2926    ($d:expr, $tmp:ident, $s:ident, $body:expr) => {
2927        match $d {
2928            Data::F64(v) => {
2929                let $s: &[f64] = v;
2930                $body
2931            }
2932            Data::I64(v) => {
2933                let $s: &[i64] = v;
2934                $body
2935            }
2936            Data::Bool(v) => {
2937                let $s: &[u8] = v;
2938                $body
2939            }
2940            other => {
2941                let $s: &[f64] = borrow_f64(other, &mut $tmp);
2942                $body
2943            }
2944        }
2945    };
2946}
2947
2948/// The same for a complex pass.
2949macro_rules! cx_source {
2950    ($d:expr, $tmp:ident, $s:ident, $body:expr) => {
2951        match $d {
2952            Data::Complex(v) => {
2953                let $s: &[Cx] = v;
2954                $body
2955            }
2956            Data::F64(v) => {
2957                let $s: &[f64] = v;
2958                $body
2959            }
2960            Data::I64(v) => {
2961                let $s: &[i64] = v;
2962                $body
2963            }
2964            Data::Bool(v) => {
2965                let $s: &[u8] = v;
2966                $body
2967            }
2968            other => {
2969                let $s: &[Cx] = borrow_cx(other, &mut $tmp);
2970                $body
2971            }
2972        }
2973    };
2974}
2975
2976/// Numeric data as f64, borrowed when it already is that.
2977fn as_f64<'a>(d: &'a Data, tmp: &'a mut Vec<f64>, span: Span) -> Result<&'a [f64]> {
2978    if !d.dtype().is_numeric() {
2979        return Err(wrong_type(d.dtype(), span));
2980    }
2981    Ok(borrow_f64(d, tmp))
2982}
2983
2984/// The type an arithmetic pair computes in. Booleans count as integers.
2985fn arith_type(a: DType, b: DType, span: Span) -> Result<DType> {
2986    if a == DType::Box || b == DType::Box {
2987        return Err(box_arith(span));
2988    }
2989    if a == DType::Symbol || b == DType::Symbol {
2990        return Err(symbol_arith(span));
2991    }
2992    match DType::promote(a, b) {
2993        Some(DType::Char) => Err(char_arith(span)),
2994        None => Err(Error::new(
2995            ErrorKind::Type,
2996            "cannot mix character and numeric data",
2997            Some(span),
2998        )),
2999        Some(DType::Bool) => Ok(DType::I64),
3000        Some(t) => Ok(t),
3001    }
3002}
3003
3004/// Apply `f` to the argument pair behind every element of one output chunk.
3005/// Element `start + k` of the result pairs `xs[xoff + (start+k)/xdiv]` with
3006/// `ys[yoff + (start+k)/ydiv]`, so broadcasting and folding both run without
3007/// materialising cells.
3008///
3009/// The two shapes that carry the work — one element per element, and one
3010/// element spread over a whole chunk — become plain loops over slices, which
3011/// is what lets the compiler vectorise the pass; anything else keeps the
3012/// general index arithmetic. `f` returns false to abandon the chunk.
3013///
3014/// The two sides carry their own element types, so a pass over operands of
3015/// different widths reads each buffer as it lies and promotes inside `f`.
3016#[allow(clippy::too_many_arguments)]
3017#[inline]
3018fn zip_chunk<A, B, U, F>(
3019    xs: &[A],
3020    xoff: usize,
3021    xdiv: usize,
3022    ys: &[B],
3023    yoff: usize,
3024    ydiv: usize,
3025    start: usize,
3026    out: &mut [U],
3027    mut f: F,
3028) -> bool
3029where
3030    A: Copy,
3031    B: Copy,
3032    F: FnMut(A, B, &mut U) -> bool,
3033{
3034    let len = out.len();
3035    if len == 0 {
3036        return true;
3037    }
3038    let last = start + len - 1;
3039    let one_x = xdiv > 1 && start / xdiv == last / xdiv;
3040    let one_y = ydiv > 1 && start / ydiv == last / ydiv;
3041    if xdiv == 1 && ydiv == 1 {
3042        let xc = &xs[xoff + start..xoff + start + len];
3043        let yc = &ys[yoff + start..yoff + start + len];
3044        for ((slot, &a), &b) in out.iter_mut().zip(xc).zip(yc) {
3045            if !f(a, b, slot) {
3046                return false;
3047            }
3048        }
3049    } else if xdiv == 1 && one_y {
3050        let b = ys[yoff + start / ydiv];
3051        let xc = &xs[xoff + start..xoff + start + len];
3052        for (slot, &a) in out.iter_mut().zip(xc) {
3053            if !f(a, b, slot) {
3054                return false;
3055            }
3056        }
3057    } else if one_x && ydiv == 1 {
3058        let a = xs[xoff + start / xdiv];
3059        let yc = &ys[yoff + start..yoff + start + len];
3060        for (slot, &b) in out.iter_mut().zip(yc) {
3061            if !f(a, b, slot) {
3062                return false;
3063            }
3064        }
3065    } else {
3066        for (k, slot) in out.iter_mut().enumerate() {
3067            let i = start + k;
3068            if !f(xs[xoff + i / xdiv], ys[yoff + i / ydiv], slot) {
3069                return false;
3070            }
3071        }
3072    }
3073    true
3074}
3075
3076// ------------------------------------------------- factorial and binomial
3077
3078/// Lanczos coefficients for g = 7, the published nine-term series.
3079const LANCZOS: [f64; 9] = [
3080    0.999_999_999_999_809_9,
3081    676.520_368_121_885_1,
3082    -1_259.139_216_722_402_8,
3083    771.323_428_777_653_1,
3084    -176.615_029_162_140_6,
3085    12.507_343_278_686_905,
3086    -0.138_571_095_265_720_12,
3087    9.984_369_578_019_572e-6,
3088    1.505_632_735_149_311_6e-7,
3089];
3090
3091/// The gamma function on the reals, by the Lanczos approximation (relative
3092/// error below 1e-13 over the range that stays finite). Poles are left to
3093/// the callers, which know the sign the limit approaches from.
3094fn gamma(x: f64) -> f64 {
3095    use std::f64::consts::PI;
3096    if x < 0.5 {
3097        // Reflection carries the negative half onto the positive one.
3098        return PI / ((PI * x).sin() * gamma(1.0 - x));
3099    }
3100    let z = x - 1.0;
3101    let mut a = LANCZOS[0];
3102    for (i, &c) in LANCZOS.iter().enumerate().skip(1) {
3103        a += c / (z + i as f64);
3104    }
3105    let t = z + 7.5;
3106    (2.0 * PI).sqrt() * t.powf(z + 0.5) * (-t).exp() * a
3107}
3108
3109/// `! y`: gamma(y+1). Integers up to 20! are exact in f64 and every
3110/// factorial is one in J, which is why this never returns an integer.
3111fn factorial(y: f64) -> f64 {
3112    if y.fract() == 0.0 && y.abs() < 1e17 {
3113        let n = y as i64;
3114        if n < 0 {
3115            // A pole: the limit alternates sign as the argument walks left.
3116            return if n % 2 == -1 { f64::INFINITY } else { f64::NEG_INFINITY };
3117        }
3118        if n > 170 {
3119            return f64::INFINITY;
3120        }
3121        let mut c = 1.0f64;
3122        for i in 2..=n {
3123            c *= i as f64;
3124        }
3125        return c;
3126    }
3127    gamma(y + 1.0)
3128}
3129
3130/// `! y` under the dialect's rule for an argument the gamma function cannot
3131/// reach at all. jconsole answers `_` wherever its own gamma overflows —
3132/// `! _`, `! 1e308` and `! _1e20` are each `_` — and refuses `! __` alone,
3133/// which is the NaN this leaves standing. The APL caller refuses every
3134/// non-finite answer and so needs no rule of its own.
3135fn factorial_as(y: f64, tol: Tol) -> f64 {
3136    let r = factorial(y);
3137    if tol.is_j() && r.is_nan() && !y.is_nan() && y != f64::NEG_INFINITY {
3138        return f64::INFINITY;
3139    }
3140    r
3141}
3142
3143/// The largest left argument the product form of the binomial is taken for;
3144/// beyond it the gamma quotient is both faster and accurate enough.
3145const BINOMIAL_PRODUCT_LIMIT: i64 = 4096;
3146
3147/// `x ! y` for a nonnegative whole x: the falling factorial over `x!`, one
3148/// factor at a time so that no partial product overflows more than the
3149/// result does.
3150fn binomial_product(x: i64, y: f64) -> f64 {
3151    let mut c = 1.0f64;
3152    for i in 1..=x {
3153        c = c * (y - i as f64 + 1.0) / i as f64;
3154        if c == 0.0 {
3155            break;
3156        }
3157    }
3158    c
3159}
3160
3161/// The two whole-number cases J answers with an exact integer: a
3162/// nonnegative x, and a negative x against a y at least as negative (the
3163/// upper-negation identity). None when the value leaves i64.
3164fn binomial_i64(x: i64, y: i64) -> Option<i64> {
3165    if x < 0 {
3166        // C(y, x) is zero for a negative x unless y is negative too and no
3167        // greater, where C(y,x) = (-1)^(y-x) C(-x-1, -y-1).
3168        if y >= 0 || y < x {
3169            return Some(0);
3170        }
3171        let v = binomial_exact(-y - 1, -x - 1)?;
3172        return if (y - x) % 2 == 0 { Some(v) } else { v.checked_neg() };
3173    }
3174    binomial_exact(x, y)
3175}
3176
3177/// `x ! y` in exact integers for a nonnegative whole x. Every partial value
3178/// is itself a binomial coefficient, so the division is always exact.
3179fn binomial_exact(x: i64, y: i64) -> Option<i64> {
3180    if x > BINOMIAL_PRODUCT_LIMIT {
3181        return None;
3182    }
3183    let mut c: i128 = 1;
3184    for i in 1..=x as i128 {
3185        c = c.checked_mul(y as i128 - i + 1)? / i;
3186        if c == 0 {
3187            break;
3188        }
3189    }
3190    i64::try_from(c).ok()
3191}
3192
3193/// `x ! y` where an operand is infinite, which the gamma quotient reaches
3194/// only as a NaN. jconsole answers most of these and refuses the rest, and
3195/// the table below is what nineteen probes of it say, entry by entry: an
3196/// infinite LEFT argument gives 0 unless the right one sits on a pole of
3197/// the gamma function; an infinite RIGHT one is read off the left's sign;
3198/// and of the four infinite pairs only `__ ! _` has a value. None is a NaN
3199/// the caller then refuses.
3200fn binomial_at_infinity(x: f64, y: f64) -> Option<f64> {
3201    if x.is_infinite() && y.is_infinite() {
3202        return (x < 0.0 && y > 0.0).then_some(0.0);
3203    }
3204    if x.is_infinite() {
3205        // `_ ! _1` and `_ ! _2` have none; `_ ! _2.5` is 0, because only a
3206        // whole negative right argument is a pole.
3207        return (!(y < 0.0 && y.fract() == 0.0)).then_some(0.0);
3208    }
3209    if x > 0.0 {
3210        Some(f64::INFINITY)
3211    } else if x == 0.0 {
3212        Some(1.0)
3213    } else {
3214        Some(0.0)
3215    }
3216}
3217
3218/// `x ! y` on the reals.
3219fn binomial(x: f64, y: f64) -> f64 {
3220    if x.is_nan() || y.is_nan() {
3221        // A NaN the program wrote travels: `_ ! _.` is `_.`, not a value
3222        // read off the table below.
3223        return f64::NAN;
3224    }
3225    if x.is_infinite() || y.is_infinite() {
3226        return binomial_at_infinity(x, y).unwrap_or(f64::NAN);
3227    }
3228    if x.fract() == 0.0 && x.abs() < 1e17 {
3229        let xi = x as i64;
3230        if xi < 0 {
3231            if y.fract() == 0.0 && y < 0.0 && y >= x {
3232                let sign = if (y as i64 - xi) % 2 == 0 { 1.0 } else { -1.0 };
3233                return sign * binomial_product(-y as i64 - 1, -x - 1.0);
3234            }
3235            return 0.0;
3236        }
3237        if xi <= BINOMIAL_PRODUCT_LIMIT {
3238            return binomial_product(xi, y);
3239        }
3240    }
3241    gamma(y + 1.0) / (gamma(x + 1.0) * gamma(y - x + 1.0))
3242}
3243
3244/// One integer step. None means the result left i64 — an overflow, or a
3245/// value that is not an integer — and the whole pass is redone in f64.
3246#[inline]
3247fn i64_op(op: ScalarDyad, a: i64, b: i64) -> Option<i64> {
3248    use ScalarDyad::*;
3249    Some(match op {
3250        Add => a.checked_add(b)?,
3251        Sub => a.checked_sub(b)?,
3252        Mul => a.checked_mul(b)?,
3253        Min => a.min(b),
3254        Max => a.max(b),
3255        Residue => {
3256            if a == 0 {
3257                b
3258            } else {
3259                // wrapping_rem: i64::MIN % -1 is mathematically 0.
3260                let mut r = b.wrapping_rem(a);
3261                if r != 0 && (r < 0) != (a < 0) {
3262                    r += a;
3263                }
3264                r
3265            }
3266        }
3267        Pow => {
3268            if b < 0 {
3269                return None;
3270            }
3271            a.checked_pow(u32::try_from(b).ok()?)?
3272        }
3273        Binomial => binomial_i64(a, b)?,
3274        _ => return None,
3275    })
3276}
3277
3278/// One float step.
3279#[inline]
3280fn f64_op(op: ScalarDyad, a: f64, b: f64, tol: Tol, span: Span) -> Result<f64> {
3281    use ScalarDyad::*;
3282    let r = match op {
3283        Add => a + b,
3284        Sub => a - b,
3285        Mul => tol.mul(a, b),
3286        Min => a.min(b),
3287        Max => a.max(b),
3288        DivJ => {
3289            if b == 0.0 {
3290                if a == 0.0 { 0.0 } else { f64::INFINITY.copysign(a) }
3291            } else {
3292                a / b
3293            }
3294        }
3295        DivApl => {
3296            if b == 0.0 {
3297                if a == 0.0 {
3298                    1.0
3299                } else {
3300                    return Err(Error::domain("division by zero", span));
3301                }
3302            } else {
3303                a / b
3304            }
3305        }
3306        Pow => {
3307            if a == 0.0 && b == 0.0 {
3308                1.0
3309            } else if a == 0.0 && b < 0.0 && !tol.is_j() {
3310                // GNU APL refuses `0⋆¯1`: it is a division by zero under
3311                // another name, and its `÷0` is refused too. J answers the
3312                // infinity, as its `% 0` does.
3313                return Err(Error::domain("zero has no negative power", span));
3314            } else if a < 0.0 && b.is_infinite() {
3315                // A negative base under an infinite exponent alternates in
3316                // sign for ever. jconsole answers only where the magnitude
3317                // falls to zero and the sign stops mattering — `_2 ^ __` is
3318                // 0 — and refuses the rest, `_1 ^ _` and `_2 ^ _` alike.
3319                if a.abs() != 1.0 && (a.abs() > 1.0) == (b < 0.0) {
3320                    0.0
3321                } else {
3322                    return Err(Error::domain(
3323                        "a negative base has no infinite power: the sign alternates",
3324                        span,
3325                    ));
3326                }
3327            } else {
3328                a.powf(b)
3329            }
3330        }
3331        Residue => tol.residue(a, b),
3332        Log => {
3333            if a < 0.0 || b < 0.0 {
3334                return Err(Error::not_yet("complex numbers", span));
3335            }
3336            let r = b.ln() / a.ln();
3337            // GNU APL has no infinite logarithm: `1⍟2`, `2⍟0` and `1⍟0` are
3338            // all DOMAIN ERROR. The two it does define where the ratio is a
3339            // NaN — `0⍟0` and `1⍟1` — are 1, each of them a base raised to
3340            // the first power. J keeps the infinity (`1 ^. 2` is `_`) and
3341            // refuses only the NaN, which the check below the match does.
3342            if !tol.is_j() && !r.is_finite() {
3343                if r.is_nan() {
3344                    return Ok(1.0);
3345                }
3346                return Err(Error::domain("this logarithm has no value", span));
3347            }
3348            r
3349        }
3350        Root => {
3351            if b < 0.0 {
3352                return Err(Error::not_yet("complex numbers", span));
3353            }
3354            b.powf(1.0 / a)
3355        }
3356        // `?`, not `return`: `1 o. _` is a NaN the arithmetic made, and
3357        // jconsole refuses it (as a limit error) rather than answering.
3358        Circle => {
3359            let r = circle(a, b, span)?;
3360            // GNU APL refuses a circle function with no value where J
3361            // continues it: `¯7○1` is artanh at its pole, an infinity in J
3362            // and a DOMAIN ERROR there.
3363            if !tol.is_j() && !r.is_finite() && a.is_finite() && b.is_finite() {
3364                return Err(Error::domain("this circle function has no value", span));
3365            }
3366            r
3367        }
3368        Binomial => binomial(a, b),
3369        _ => return Err(Error::internal("non-arithmetic op in the float path")),
3370    };
3371    if tol.made_nan(r, a, b) {
3372        return Err(nan_error(op, a, b, span));
3373    }
3374    Ok(r)
3375}
3376
3377/// The diagnostic for arithmetic with no value, naming the pair that has
3378/// none: "NaN error: `_ - _` has no value".
3379#[cold]
3380fn nan_error(op: ScalarDyad, a: f64, b: f64, span: Span) -> Error {
3381    Error::nan(
3382        format!(
3383            "`{} {} {}` has no value",
3384            j_number(a),
3385            crate::fuse::dyad_name(op),
3386            j_number(b)
3387        ),
3388        span,
3389    )
3390}
3391
3392/// Which of a real pair's operations has no real answer, so the whole pass
3393/// runs in the complex domain instead. Only the four operations that can
3394/// leave the reals are asked.
3395#[inline]
3396fn escapes_reals(op: ScalarDyad, a: f64, b: f64) -> bool {
3397    use ScalarDyad::*;
3398    match op {
3399        // An integer exponent keeps a negative base real (`_1 ^ 2` is 1).
3400        // An INFINITE one is neither integer nor fractional: `fract` is a
3401        // NaN there, and the pair belongs to the real path, which answers
3402        // `_2 ^ __` with 0 and refuses the rest.
3403        Pow => a < 0.0 && b.is_finite() && b.fract() != 0.0,
3404        Log => a < 0.0 || b < 0.0,
3405        Root => b < 0.0,
3406        Circle => circle_escapes(a, b),
3407        _ => false,
3408    }
3409}
3410
3411/// The circle functions with no real answer at a real argument. A
3412/// non-integer k is a domain error, which the real path reports.
3413#[inline]
3414fn circle_escapes(k: f64, y: f64) -> bool {
3415    if k.fract() != 0.0 {
3416        return false;
3417    }
3418    match k as i64 {
3419        0 | -1 | -2 | -7 => y.abs() > 1.0,
3420        -4 => y.abs() < 1.0,
3421        -6 => y < 1.0,
3422        // The functions built on the imaginary unit, which no real argument
3423        // escapes.
3424        8 | -8 | -11 | -12 => true,
3425        _ => false,
3426    }
3427}
3428
3429/// `k o. y`: the circle function k applied to a real y.
3430///
3431/// The table is J's and APL's alike (they share it): 1 2 3 are sine, cosine
3432/// and tangent, 5 6 7 their hyperbolic counterparts, a negative k inverts the
3433/// function at |k|, and 0 and 4 are the two Pythagorean forms. 9 to 12 read
3434/// the parts of a complex number — real, magnitude, imaginary, phase — and
3435/// are answered here for the reals they also accept. A pair whose answer
3436/// leaves the reals never reaches this function: [`escapes_reals`] sends the
3437/// whole pass to the complex path first.
3438#[inline]
3439fn circle(k: f64, y: f64, span: Span) -> Result<f64> {
3440    if k.fract() != 0.0 {
3441        return Err(Error::domain("the circle function needs an integer left argument", span));
3442    }
3443    let complex = || Error::internal("a circle function left the reals on the real path");
3444    Ok(match k as i64 {
3445        0 => {
3446            if y.abs() > 1.0 {
3447                return Err(complex());
3448            }
3449            (1.0 - y * y).max(0.0).sqrt()
3450        }
3451        1 => y.sin(),
3452        2 => y.cos(),
3453        3 => y.tan(),
3454        4 => (1.0 + y * y).sqrt(),
3455        5 => y.sinh(),
3456        6 => y.cosh(),
3457        7 => y.tanh(),
3458        -1 => {
3459            if y.abs() > 1.0 {
3460                return Err(complex());
3461            }
3462            y.asin()
3463        }
3464        -2 => {
3465            if y.abs() > 1.0 {
3466                return Err(complex());
3467            }
3468            y.acos()
3469        }
3470        -3 => y.atan(),
3471        -4 => {
3472            if y.abs() < 1.0 {
3473                return Err(complex());
3474            }
3475            // The sign follows y: `_4 o. _2` is `_1.73205`, not `1.73205`.
3476            y.signum() * (y * y - 1.0).max(0.0).sqrt()
3477        }
3478        -5 => y.asinh(),
3479        -6 => {
3480            if y < 1.0 {
3481                return Err(complex());
3482            }
3483            y.acosh()
3484        }
3485        -7 => {
3486            if y.abs() > 1.0 {
3487                return Err(complex());
3488            }
3489            y.atanh()
3490        }
3491        // The parts of a number that happens to be real.
3492        9 | -9 | -10 => y,
3493        10 => y.abs(),
3494        11 => 0.0,
3495        12 => {
3496            if y < 0.0 {
3497                std::f64::consts::PI
3498            } else {
3499                0.0
3500            }
3501        }
3502        8 | -8 | -11 | -12 => return Err(complex()),
3503        _ => {
3504            return Err(Error::domain(
3505                "the circle functions run from _12 to 12",
3506                span,
3507            ));
3508        }
3509    })
3510}
3511
3512/// One complex step.
3513#[inline]
3514fn cx_op(op: ScalarDyad, a: Cx, b: Cx, span: Span) -> Result<Cx> {
3515    use ScalarDyad::*;
3516    Ok(match op {
3517        Add => cx::add(a, b),
3518        Sub => cx::sub(a, b),
3519        Mul => cx::mul(a, b),
3520        DivJ => cx::div(a, b),
3521        DivApl => {
3522            if b == cx::ZERO {
3523                if a == cx::ZERO {
3524                    cx::ONE
3525                } else {
3526                    return Err(Error::domain("division by zero", span));
3527                }
3528            } else {
3529                cx::div(a, b)
3530            }
3531        }
3532        Pow => cx::pow(a, b),
3533        Log => cx::log(a, b),
3534        Root => cx::root(a, b),
3535        Residue => cx::residue(a, b),
3536        Lcm => cx::lcm(a, b),
3537        Gcd => cx::gcd(a, b),
3538        MakeComplex => cx::add(a, cx::mul(cx::I, b)),
3539        PolarBy => cx::mul(a, cx::exp(cx::mul(cx::I, b))),
3540        Circle => {
3541            if a[1] != 0.0 || a[0].fract() != 0.0 {
3542                return Err(Error::domain(
3543                    "the circle function needs an integer left argument",
3544                    span,
3545                ));
3546            }
3547            cx::circle(a[0] as i64, b).ok_or_else(|| {
3548                Error::domain("the circle functions run from _12 to 12", span)
3549            })?
3550        }
3551        Min | Max => return Err(no_complex_order(span)),
3552        Binomial => {
3553            return Err(Error::not_yet("the binomial function on complex numbers", span));
3554        }
3555        Eq | Ne | Lt | Le | Gt | Ge => {
3556            return Err(Error::internal("a comparison in the complex arithmetic path"));
3557        }
3558    })
3559}
3560
3561/// The complaint an ordering makes about complex operands. Both references
3562/// refuse it: complex numbers carry no order, only equality.
3563fn no_complex_order(span: Span) -> Error {
3564    Error::new(
3565        ErrorKind::Domain,
3566        "complex numbers have no order; only equality (=, ~:) applies to them",
3567        Some(span),
3568    )
3569}
3570
3571#[allow(clippy::too_many_arguments)]
3572#[inline(always)]
3573fn dyad_cx_chunk_body<A: Widen<Cx>, B: Widen<Cx>>(
3574    op: ScalarDyad,
3575    xs: &[A],
3576    xoff: usize,
3577    xdiv: usize,
3578    ys: &[B],
3579    yoff: usize,
3580    ydiv: usize,
3581    start: usize,
3582    out: &mut [Cx],
3583    span: Span,
3584) -> Result<()> {
3585    use ScalarDyad::*;
3586    // The three steps that cannot fail are picked before the loop, so the
3587    // pass is one operation per element rather than a match per element.
3588    macro_rules! plain {
3589        ($step:expr) => {{
3590            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut Cx| {
3591                *slot = $step(a.widen(), b.widen());
3592                true
3593            });
3594            return Ok(());
3595        }};
3596    }
3597    match op {
3598        Add => plain!(cx::add),
3599        Sub => plain!(cx::sub),
3600        Mul => plain!(cx::mul),
3601        DivJ => plain!(cx::div),
3602        _ => {}
3603    }
3604    let mut err = None;
3605    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut Cx| {
3606        match cx_op(op, a.widen(), b.widen(), span) {
3607            Ok(v) => {
3608                *slot = v;
3609                true
3610            }
3611            Err(e) => {
3612                err = Some(e);
3613                false
3614            }
3615        }
3616    });
3617    match err {
3618        Some(e) => Err(e),
3619        None => Ok(()),
3620    }
3621}
3622
3623multiversioned! {
3624    /// One chunk of a complex pass, compiled per CPU feature level. Either
3625    /// operand may be narrower than complex, and is promoted as it is read.
3626    #[allow(clippy::too_many_arguments)]
3627    fn dyad_cx_chunk[A: Widen<Cx>, B: Widen<Cx>](
3628        op: ScalarDyad,
3629        xs: &[A],
3630        xoff: usize,
3631        xdiv: usize,
3632        ys: &[B],
3633        yoff: usize,
3634        ydiv: usize,
3635        start: usize,
3636        out: &mut [Cx],
3637        span: Span,
3638    ) -> Result<()> = dyad_cx_chunk_body;
3639}
3640
3641#[allow(clippy::too_many_arguments)]
3642fn dyad_cx<A: Widen<Cx>, B: Widen<Cx>>(
3643    op: ScalarDyad,
3644    xs: &[A],
3645    xoff: usize,
3646    xdiv: usize,
3647    ys: &[B],
3648    yoff: usize,
3649    ydiv: usize,
3650    n: usize,
3651    span: Span,
3652) -> Result<Vec<Cx>> {
3653    par::try_fill(n, |start, part| {
3654        dyad_cx_chunk(op, xs, xoff, xdiv, ys, yoff, ydiv, start, part, span)
3655    })
3656}
3657
3658/// One complex pass over two buffers.
3659///
3660/// An operand that is not complex already is read in its own type and
3661/// promoted element by element, so the pass allocates nothing but its
3662/// result. Only the exact types, which have no fixed-width buffer, are
3663/// widened into one first — and a pass with no complex operand at all (`j.`
3664/// of two reals, a power that leaves the reals) with them, since promoting
3665/// two whole buffers is what such a pass is for.
3666#[allow(clippy::too_many_arguments)]
3667fn complex_dyad_data(
3668    op: ScalarDyad,
3669    x: &Data,
3670    xoff: usize,
3671    xdiv: usize,
3672    y: &Data,
3673    yoff: usize,
3674    ydiv: usize,
3675    n: usize,
3676    span: Span,
3677) -> Result<Data> {
3678    let (mut tx, mut ty) = (Vec::new(), Vec::new());
3679    macro_rules! pass {
3680        ($xs:expr, $ys:expr) => {
3681            Data::Complex(dyad_cx(op, $xs, xoff, xdiv, $ys, yoff, ydiv, n, span)?.into())
3682        };
3683    }
3684    Ok(match (x, y) {
3685        (Data::Complex(a), _) => {
3686            let xs: &[Cx] = a;
3687            cx_source!(y, ty, ys, pass!(xs, ys))
3688        }
3689        (_, Data::Complex(b)) => {
3690            let ys: &[Cx] = b;
3691            cx_source!(x, tx, xs, pass!(xs, ys))
3692        }
3693        _ => pass!(borrow_cx(x, &mut tx), borrow_cx(y, &mut ty)),
3694    })
3695}
3696
3697/// `9 o.` to `12 o.` read a part of a number — real, magnitude, imaginary,
3698/// phase — so their answers are real however complex the argument was. J
3699/// reports them as floats rather than as complex values with a zero
3700/// imaginary part.
3701fn circle_reads_a_part(x: &Data, xoff: usize, xdiv: usize, n: usize) -> bool {
3702    if x.dtype() == DType::Complex {
3703        // A complex left argument selects nothing; the pass reports it.
3704        return false;
3705    }
3706    let mut tmp = Vec::new();
3707    let xs = borrow_f64(x, &mut tmp);
3708    (0..n).all(|i| {
3709        let k = xs[xoff + i / xdiv];
3710        k.fract() == 0.0 && (9.0..=12.0).contains(&k)
3711    })
3712}
3713
3714/// Does the real pass hold an argument pair whose answer leaves the reals?
3715/// One extra scan, and only for the four operations that can.
3716#[allow(clippy::too_many_arguments)]
3717fn pass_leaves_reals(
3718    op: ScalarDyad,
3719    x: &Data,
3720    xoff: usize,
3721    xdiv: usize,
3722    y: &Data,
3723    yoff: usize,
3724    ydiv: usize,
3725    n: usize,
3726) -> bool {
3727    use ScalarDyad::*;
3728    if !matches!(op, Pow | Log | Root | Circle) {
3729        return false;
3730    }
3731    let (mut tx, mut ty) = (Vec::new(), Vec::new());
3732    let xs = borrow_f64(x, &mut tx);
3733    let ys = borrow_f64(y, &mut ty);
3734    (0..n).any(|i| escapes_reals(op, xs[xoff + i / xdiv], ys[yoff + i / ydiv]))
3735}
3736
3737#[allow(clippy::too_many_arguments)]
3738#[inline(always)]
3739fn dyad_i64_chunk_body<A: Widen<i64>, B: Widen<i64>>(
3740    op: ScalarDyad,
3741    xs: &[A],
3742    xoff: usize,
3743    xdiv: usize,
3744    ys: &[B],
3745    yoff: usize,
3746    ydiv: usize,
3747    start: usize,
3748    out: &mut [i64],
3749) -> bool {
3750    use ScalarDyad::*;
3751    // The overflow of the three growing operations is folded into a flag
3752    // rather than breaking the loop: that keeps the pass branch-free, and an
3753    // overflowing chunk is thrown away and redone in f64 in any case.
3754    macro_rules! overflowing {
3755        ($m:ident) => {{
3756            let mut over = false;
3757            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut i64| {
3758                let (v, o) = i64::$m(a.widen(), b.widen());
3759                *slot = v;
3760                over |= o;
3761                true
3762            });
3763            !over
3764        }};
3765    }
3766    macro_rules! plain {
3767        ($step:expr) => {{
3768            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut i64| {
3769                *slot = $step(a.widen(), b.widen());
3770                true
3771            })
3772        }};
3773    }
3774    match op {
3775        Add => overflowing!(overflowing_add),
3776        Sub => overflowing!(overflowing_sub),
3777        Mul => overflowing!(overflowing_mul),
3778        Min => plain!(i64::min),
3779        Max => plain!(i64::max),
3780        _ => zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut i64| {
3781            match i64_op(op, a.widen(), b.widen()) {
3782                Some(v) => {
3783                    *slot = v;
3784                    true
3785                }
3786                None => false,
3787            }
3788        }),
3789    }
3790}
3791
3792multiversioned! {
3793    /// One chunk of an integer pass. False means the chunk left i64 and the
3794    /// caller redoes the whole operation in f64.
3795    ///
3796    /// This is one of the loops compiled per CPU feature level: a chunk is
3797    /// thousands of elements, so choosing the compilation costs nothing
3798    /// against the pass it chooses.
3799    #[allow(clippy::too_many_arguments)]
3800    fn dyad_i64_chunk[A: Widen<i64>, B: Widen<i64>](
3801        op: ScalarDyad,
3802        xs: &[A],
3803        xoff: usize,
3804        xdiv: usize,
3805        ys: &[B],
3806        yoff: usize,
3807        ydiv: usize,
3808        start: usize,
3809        out: &mut [i64],
3810    ) -> bool = dyad_i64_chunk_body;
3811}
3812
3813/// One elementwise integer pass. None means it left i64 anywhere.
3814#[allow(clippy::too_many_arguments)]
3815fn dyad_i64<A: Widen<i64>, B: Widen<i64>>(
3816    op: ScalarDyad,
3817    xs: &[A],
3818    xoff: usize,
3819    xdiv: usize,
3820    ys: &[B],
3821    yoff: usize,
3822    ydiv: usize,
3823    n: usize,
3824) -> Option<Vec<i64>> {
3825    let (out, ok) = par::fill(n, |start, part| {
3826        dyad_i64_chunk(op, xs, xoff, xdiv, ys, yoff, ydiv, start, part)
3827    });
3828    ok.then_some(out)
3829}
3830
3831/// One elementwise integer pass over two buffers, each read in its own
3832/// element type. None means it left i64 anywhere.
3833#[allow(clippy::too_many_arguments)]
3834fn int_dyad_data(
3835    op: ScalarDyad,
3836    x: &Data,
3837    xoff: usize,
3838    xdiv: usize,
3839    y: &Data,
3840    yoff: usize,
3841    ydiv: usize,
3842    n: usize,
3843) -> Option<Data> {
3844    let (mut tx, mut ty) = (Vec::new(), Vec::new());
3845    let out = i64_source!(x, tx, xs, {
3846        i64_source!(y, ty, ys, dyad_i64(op, xs, xoff, xdiv, ys, yoff, ydiv, n))
3847    })?;
3848    Some(Data::I64(out.into()))
3849}
3850
3851#[allow(clippy::too_many_arguments)]
3852#[inline(always)]
3853fn dyad_f64_chunk_body<A: Widen<f64>, B: Widen<f64>>(
3854    op: ScalarDyad,
3855    xs: &[A],
3856    xoff: usize,
3857    xdiv: usize,
3858    ys: &[B],
3859    yoff: usize,
3860    ydiv: usize,
3861    start: usize,
3862    out: &mut [f64],
3863    tol: Tol,
3864    span: Span,
3865) -> Result<()> {
3866    use ScalarDyad::*;
3867    // The arithmetic that cannot fail is picked before the loop, so the
3868    // compiler sees one operation per pass instead of a match per element.
3869    macro_rules! plain {
3870        ($step:expr) => {{
3871            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut f64| {
3872                *slot = $step(a.widen(), b.widen());
3873                true
3874            });
3875            return Ok(());
3876        }};
3877    }
3878    // The arithmetic that cannot fail runs in the plain loop; under J's
3879    // rules a NaN in what it wrote means the pass has to be redone one pair
3880    // at a time, because only there are both operands in hand to tell a NaN
3881    // the arithmetic MADE from one the program wrote. The scan itself
3882    // vectorises and finds nothing on ordinary data, so the fast path keeps
3883    // its speed and the slow one keeps the rule.
3884    macro_rules! plain_checked {
3885        ($step:expr) => {{
3886            zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut f64| {
3887                *slot = $step(a.widen(), b.widen());
3888                true
3889            });
3890            if !(tol.is_j() && out.iter().any(|v| v.is_nan())) {
3891                return Ok(());
3892            }
3893        }};
3894    }
3895    match op {
3896        Add => plain_checked!(|a: f64, b: f64| a + b),
3897        Sub => plain_checked!(|a: f64, b: f64| a - b),
3898        Mul => plain_checked!(|a: f64, b: f64| a * b),
3899        Min => plain!(f64::min),
3900        Max => plain!(f64::max),
3901        _ => {}
3902    }
3903    let mut err = None;
3904    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut f64| {
3905        match f64_op(op, a.widen(), b.widen(), tol, span) {
3906            Ok(v) => {
3907                *slot = v;
3908                true
3909            }
3910            Err(e) => {
3911                err = Some(e);
3912                false
3913            }
3914        }
3915    });
3916    match err {
3917        Some(e) => Err(e),
3918        None => Ok(()),
3919    }
3920}
3921
3922multiversioned! {
3923    /// One chunk of a float pass, compiled per CPU feature level. Either
3924    /// operand may be an integer or a boolean buffer, promoted as it is read.
3925    #[allow(clippy::too_many_arguments)]
3926    fn dyad_f64_chunk[A: Widen<f64>, B: Widen<f64>](
3927        op: ScalarDyad,
3928        xs: &[A],
3929        xoff: usize,
3930        xdiv: usize,
3931        ys: &[B],
3932        yoff: usize,
3933        ydiv: usize,
3934        start: usize,
3935        out: &mut [f64],
3936        tol: Tol,
3937        span: Span,
3938    ) -> Result<()> = dyad_f64_chunk_body;
3939}
3940
3941#[allow(clippy::too_many_arguments)]
3942fn dyad_f64<A: Widen<f64>, B: Widen<f64>>(
3943    op: ScalarDyad,
3944    xs: &[A],
3945    xoff: usize,
3946    xdiv: usize,
3947    ys: &[B],
3948    yoff: usize,
3949    ydiv: usize,
3950    n: usize,
3951    tol: Tol,
3952    span: Span,
3953) -> Result<Vec<f64>> {
3954    par::try_fill(n, |start, part| {
3955        dyad_f64_chunk(op, xs, xoff, xdiv, ys, yoff, ydiv, start, part, tol, span)
3956    })
3957}
3958
3959/// One float pass over two buffers, each read in its own element type.
3960#[allow(clippy::too_many_arguments)]
3961fn float_dyad_data(
3962    op: ScalarDyad,
3963    x: &Data,
3964    xoff: usize,
3965    xdiv: usize,
3966    y: &Data,
3967    yoff: usize,
3968    ydiv: usize,
3969    n: usize,
3970    tol: Tol,
3971    span: Span,
3972) -> Result<Data> {
3973    let (mut tx, mut ty) = (Vec::new(), Vec::new());
3974    let out = f64_source!(x, tx, xs, {
3975        f64_source!(y, ty, ys, dyad_f64(op, xs, xoff, xdiv, ys, yoff, ydiv, n, tol, span)?)
3976    });
3977    Ok(Data::F64(out.into()))
3978}
3979
3980/// Whether two element types have nothing in common to compare: a
3981/// character against a number, or a box against either. Two numeric types
3982/// always meet somewhere, however far apart the widths are.
3983fn crossed_types(a: DType, b: DType) -> bool {
3984    let class = |d: DType| match d {
3985        DType::Box => 3,
3986        DType::Symbol => 2,
3987        DType::Char => 1,
3988        _ => 0,
3989    };
3990    class(a) != class(b)
3991}
3992
3993/// `x <. y` and `x >. y` over symbols: the smaller or larger NAME of the
3994/// pair, which is the only arithmetic a symbol has.
3995#[allow(clippy::too_many_arguments)]
3996fn symbol_min_max(
3997    op: ScalarDyad,
3998    x: &Data,
3999    xoff: usize,
4000    xdiv: usize,
4001    y: &Data,
4002    yoff: usize,
4003    ydiv: usize,
4004    n: usize,
4005    span: Span,
4006) -> Result<Data> {
4007    let (Data::Symbol(a), Data::Symbol(b)) = (x, y) else {
4008        return Err(symbol_arith(span));
4009    };
4010    let down = op == ScalarDyad::Min;
4011    let (out, _) = par::fill(n, |start, part: &mut [crate::symbol::Id]| {
4012        zip_chunk(a, xoff, xdiv, b, yoff, ydiv, start, part, |p, q, slot| {
4013            *slot = if crate::symbol::cmp(p, q).is_le() == down { p } else { q };
4014            true
4015        })
4016    });
4017    Ok(Data::Symbol(out.into()))
4018}
4019
4020#[allow(clippy::too_many_arguments)]
4021fn compare_data(
4022    op: ScalarDyad,
4023    x: &Data,
4024    xoff: usize,
4025    xdiv: usize,
4026    y: &Data,
4027    yoff: usize,
4028    ydiv: usize,
4029    n: usize,
4030    tol: Tol,
4031    span: Span,
4032) -> Result<Data> {
4033    use ScalarDyad::*;
4034    let (dx, dy) = (x.dtype(), y.dtype());
4035    let equality = matches!(op, Eq | Ne);
4036    // Equality is TOTAL across a character and a number in both
4037    // references: `'a' = 1` is 0. It is total across the BOX boundary in J
4038    // too — `(<1) = 1` is 0 — but not in APL, where a scalar verb reaches
4039    // inside the box instead, so that case falls through to the diagnostic
4040    // below rather than answering 0.
4041    let boxed = dx == DType::Box || dy == DType::Box;
4042    if equality && crossed_types(dx, dy) && (!boxed || tol.is_j()) {
4043        let unequal = op == Ne;
4044        return Ok(Data::Bool(vec![u8::from(unequal); n].into()));
4045    }
4046    if boxed {
4047        // Boxes have no order — J refuses `<` on them — but they do have
4048        // equality, which compares their contents.
4049        if !equality {
4050            return Err(box_arith(span));
4051        }
4052        let (Data::Box(a), Data::Box(b)) = (x, y) else {
4053            // Only APL reaches here: its scalar verbs pervade into a
4054            // nested argument, which is a promise rather than a refusal.
4055            return Err(Error::not_yet("a scalar function inside a nested array", span));
4056        };
4057        let (out, _) = par::fill(n, |start, part: &mut [u8]| {
4058            for (k, slot) in part.iter_mut().enumerate() {
4059                let i = start + k;
4060                let e = arrays_match(&a[xoff + i / xdiv], &b[yoff + i / ydiv], tol);
4061                *slot = u8::from(if op == Eq { e } else { !e });
4062            }
4063            true
4064        });
4065        return Ok(Data::Bool(out.into()));
4066    }
4067    if dx == DType::Symbol || dy == DType::Symbol {
4068        // Equality across the boundary answered above; anything else here
4069        // is an ordering that has nothing to order against.
4070        if dx != dy {
4071            return Err(Error::new(
4072                ErrorKind::Type,
4073                "cannot compare a symbol with data that is not a symbol",
4074                Some(span),
4075            ));
4076        }
4077        let (Data::Symbol(a), Data::Symbol(b)) = (x, y) else {
4078            return Err(Error::internal("symbol comparison on non-symbol data"));
4079        };
4080        // Ordering reads the names; equality is index against index.
4081        let (out, _) = par::fill(n, |start, part: &mut [u8]| {
4082            zip_chunk(a, xoff, xdiv, b, yoff, ydiv, start, part, |p, q, slot| {
4083                *slot = u8::from(match op {
4084                    Eq => p == q,
4085                    Ne => p != q,
4086                    _ => {
4087                        let o = crate::symbol::cmp(p, q);
4088                        match op {
4089                            Lt => o.is_lt(),
4090                            Le => o.is_le(),
4091                            Gt => o.is_gt(),
4092                            _ => o.is_ge(),
4093                        }
4094                    }
4095                });
4096                true
4097            })
4098        });
4099        return Ok(Data::Bool(out.into()));
4100    }
4101    if dx == DType::Char || dy == DType::Char {
4102        if dx != dy {
4103            return Err(Error::new(
4104                ErrorKind::Type,
4105                "cannot compare character and numeric data",
4106                Some(span),
4107            ));
4108        }
4109        if !equality {
4110            return Err(Error::new(
4111                ErrorKind::Type,
4112                "cannot order character data; only equality applies",
4113                Some(span),
4114            ));
4115        }
4116        let (Data::Char(a), Data::Char(b)) = (x, y) else {
4117            return Err(Error::internal("character comparison on non-character data"));
4118        };
4119        let (out, _) = par::fill(n, |start, part: &mut [u8]| {
4120            zip_chunk(a, xoff, xdiv, b, yoff, ydiv, start, part, |p, q, slot| {
4121                let e = p == q;
4122                *slot = if op == Eq { e as u8 } else { !e as u8 };
4123                true
4124            })
4125        });
4126        return Ok(Data::Bool(out.into()));
4127    }
4128    if DType::promote(dx, dy).is_some_and(DType::is_exact)
4129        && let Some(d) = exact_compare_data(op, x, xoff, xdiv, y, yoff, ydiv, n)
4130    {
4131        return Ok(d);
4132    }
4133    if dx == DType::Complex || dy == DType::Complex {
4134        if !equality {
4135            return Err(no_complex_order(span));
4136        }
4137        let (mut tx, mut ty) = (Vec::new(), Vec::new());
4138        let out = cx_source!(x, tx, xs, {
4139            cx_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                        let e = tol.eq_cx(a.widen(), b.widen());
4143                        *slot = if op == Eq { e as u8 } else { !e as u8 };
4144                        true
4145                    })
4146                })
4147                .0
4148            })
4149        });
4150        return Ok(Data::Bool(out.into()));
4151    }
4152    // Floats compare with the dialect's tolerance; integers are exact
4153    // whatever it is, so the integer pass below is untouched by it.
4154    let out = if DType::promote(dx, dy) == Some(DType::F64) {
4155        let (mut tx, mut ty) = (Vec::<f64>::new(), Vec::<f64>::new());
4156        f64_source!(x, tx, xs, {
4157            f64_source!(y, ty, ys, {
4158                par::fill(n, |start, part: &mut [u8]| {
4159                    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, part, |a, b, slot| {
4160                        *slot = tol_cmp(op, a.widen(), b.widen(), tol) as u8;
4161                        true
4162                    })
4163                })
4164                .0
4165            })
4166        })
4167    } else {
4168        let (mut tx, mut ty) = (Vec::<i64>::new(), Vec::<i64>::new());
4169        i64_source!(x, tx, xs, {
4170            i64_source!(y, ty, ys, {
4171                par::fill(n, |start, part: &mut [u8]| {
4172                    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, part, |a, b, slot| {
4173                        let (a, b): (i64, i64) = (a.widen(), b.widen());
4174                        *slot = cmp_result(op, Some(i64::cmp(&a, &b))) as u8;
4175                        true
4176                    })
4177                })
4178                .0
4179            })
4180        })
4181    };
4182    Ok(Data::Bool(out.into()))
4183}
4184
4185/// One tolerant float comparison.
4186#[inline(always)]
4187pub(crate) fn tol_cmp(op: ScalarDyad, a: f64, b: f64, tol: Tol) -> bool {
4188    use ScalarDyad::*;
4189    match op {
4190        Eq => tol.eq(a, b),
4191        Ne => !tol.eq(a, b),
4192        Lt => tol.lt(a, b),
4193        Le => tol.le(a, b),
4194        Gt => tol.lt(b, a),
4195        Ge => tol.le(b, a),
4196        _ => false,
4197    }
4198}
4199
4200/// Two floats ordered under a tolerance: values that are tolerantly equal
4201/// tie, which is what leaves them in their original order in a stable sort.
4202/// A NaN ties with everything, which keeps the sort total.
4203#[inline]
4204pub(crate) fn tol_ord(a: f64, b: f64, tol: Tol) -> std::cmp::Ordering {
4205    use std::cmp::Ordering::Equal;
4206    if tol.ct != 0.0 && tol.eq(a, b) {
4207        return Equal;
4208    }
4209    a.partial_cmp(&b).unwrap_or(Equal)
4210}
4211
4212/// Turn an ordering (None for NaN) into a comparison result.
4213fn cmp_result(op: ScalarDyad, ord: Option<std::cmp::Ordering>) -> bool {
4214    use std::cmp::Ordering::*;
4215    use ScalarDyad::*;
4216    match ord {
4217        None => matches!(op, Ne),
4218        Some(o) => match op {
4219            Eq => o == Equal,
4220            Ne => o != Equal,
4221            Lt => o == Less,
4222            Le => o != Greater,
4223            Gt => o == Greater,
4224            Ge => o != Less,
4225            _ => false,
4226        },
4227    }
4228}
4229
4230/// Greatest common divisor, always nonnegative; `gcd(0, 0)` is 0.
4231///
4232/// GNU APL parts company here when one side is zero: `¯3∨0` and `0∨¯3` are
4233/// both `¯3` there, the other argument returned unchanged with its sign,
4234/// where J answers `3`. [`signed_gcd_i128`] is the APL reading.
4235fn gcd_i128(a: i128, b: i128) -> i128 {
4236    let (mut a, mut b) = (a.abs(), b.abs());
4237    while b != 0 {
4238        let t = a % b;
4239        a = b;
4240        b = t;
4241    }
4242    a
4243}
4244
4245/// GNU APL's GCD: the magnitude, except that a zero argument hands back
4246/// the other one untouched, sign and all. Only whole numbers keep the sign
4247/// — `¯3.5∨0` is `3.5` in GNU, so the real path below stays nonnegative.
4248fn signed_gcd_i128(a: i128, b: i128) -> i128 {
4249    match (a, b) {
4250        (0, _) => b,
4251        (_, 0) => a,
4252        _ => gcd_i128(a, b),
4253    }
4254}
4255
4256/// A finite float as `p / 10^s`, read off the shortest decimal that prints
4257/// back as this value — which is the number the user wrote and the number
4258/// both references show.
4259///
4260/// A value needing more than [`WRITTEN_DIGITS`] significant digits is not a
4261/// number anyone wrote: it is the residue of an arithmetic that missed, and
4262/// reading it as a decimal turns a rounding error into a divisor.
4263fn decimal_parts(v: f64) -> Option<(i128, u32)> {
4264    if !v.is_finite() {
4265        return None;
4266    }
4267    let text = format!("{v:e}");
4268    let (mantissa, exponent) = text.split_once('e')?;
4269    let exponent: i32 = exponent.parse().ok()?;
4270    let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
4271    if whole.trim_start_matches('-').len() + fraction.len() > WRITTEN_DIGITS {
4272        return None;
4273    }
4274    let mut digits: i128 = format!("{whole}{fraction}").parse().ok()?;
4275    let mut scale = fraction.len() as i32 - exponent;
4276    // A negative scale is a whole number with trailing zeros; fold them in
4277    // so every value arrives as `p / 10^s` with s at least zero.
4278    while scale < 0 {
4279        digits = digits.checked_mul(10)?;
4280        scale += 1;
4281    }
4282    // Beyond this the products below leave i128, and the Euclid fallback
4283    // takes over.
4284    (scale <= 34).then_some((digits, scale as u32))
4285}
4286
4287/// How many significant digits a decimal the user typed may need. Twelve
4288/// leaves every written constant intact and rejects the rounding residues:
4289/// `0.1+0.2` prints back as seventeen digits, `1.0000000000001` as fourteen.
4290const WRITTEN_DIGITS: usize = 12;
4291
4292/// The GCD of two reals read as the decimals they are printed as: `1.23`
4293/// and `4.56` are 123 and 456 hundredths, so their GCD is three hundredths.
4294/// That is the value both references print — theirs is the Euclid grind
4295/// that rounds to it, and a binary Euclid of our own cannot reach either.
4296fn gcd_decimal(a: f64, b: f64) -> Option<f64> {
4297    let (pa, sa) = decimal_parts(a)?;
4298    let (pb, sb) = decimal_parts(b)?;
4299    let scale = sa.max(sb);
4300    let lift = |p: i128, s: u32| 10i128.checked_pow(scale - s).and_then(|k| p.checked_mul(k));
4301    let g = gcd_i128(lift(pa, sa)?, lift(pb, sb)?);
4302    // Dividing through a decimal string keeps the one rounding the value
4303    // itself carries, where a multiply by 10^s of its own would add another.
4304    format!("{g}e-{scale}").parse().ok()
4305}
4306
4307/// The real GCD, by Euclid on the values themselves. Floats cannot reach an
4308/// exact zero remainder, so a remainder is taken to be zero once it is
4309/// within the comparison tolerance of the LARGER argument — the scale the
4310/// whole division sequence was measured against — or of the divisor, which
4311/// is the same step seen from the other end. That is what makes
4312/// `0.1 +. 0.2` answer `0.1` and `0.3 +. 0.1+0.2` answer `0.3` rather than
4313/// grinding down to a rounding error.
4314fn gcd_f64(a: f64, b: f64, tol: Tol) -> Option<f64> {
4315    let (mut a, mut b) = (a.abs(), b.abs());
4316    if !a.is_finite() || !b.is_finite() {
4317        return None;
4318    }
4319    let eps = tol.ct * a.max(b);
4320    // Euclid on reals converges as fast as it does on integers; the bound
4321    // is a guard, not the usual exit.
4322    for _ in 0..1000 {
4323        if b == 0.0 {
4324            return Some(a);
4325        }
4326        if a == 0.0 {
4327            return Some(b);
4328        }
4329        // The quotient's floor is TOLERANT, as J's `<.` is: a quotient a
4330        // rounding error below an integer is that integer, and the step
4331        // then lands on a remainder of zero instead of on the divisor. What
4332        // is left can only fall just outside [0, b), so it is clamped.
4333        let q = a / b;
4334        let mut k = q.floor();
4335        if tol.eq(q, k + 1.0) {
4336            k += 1.0;
4337        }
4338        let mut r = a - b * k;
4339        if r <= eps || tol.eq(r, b) {
4340            r = 0.0;
4341        }
4342        a = b;
4343        b = r;
4344    }
4345    Some(a)
4346}
4347
4348/// The real LCM/GCD pass: Euclid on the values, which is what J answers for
4349/// a pair that is not whole. An infinite operand has no answer, and both
4350/// references refuse it.
4351#[allow(clippy::too_many_arguments)]
4352fn real_lcm_gcd(
4353    op: ScalarDyad,
4354    xs: &[f64],
4355    xoff: usize,
4356    xdiv: usize,
4357    ys: &[f64],
4358    yoff: usize,
4359    ydiv: usize,
4360    n: usize,
4361    tol: Tol,
4362    gnu: bool,
4363    span: Span,
4364) -> Result<Data> {
4365    let mut out = vec![0.0f64; n];
4366    let mut ok = true;
4367    // GNU APL reads an operand within `⎕CT` of a whole number as that
4368    // number before anything else: `1.0000000000001∧5` is 5 there, not the
4369    // 5e13 the unrounded value grinds out. J does no such thing —
4370    // `1.0000000000001 +. 1` is `9.99e_14` in jconsole.
4371    let whole = |v: f64| {
4372        let w = v.round();
4373        if gnu && tol.eq(v, w) { w } else { v }
4374    };
4375    // And an operand no larger than `⎕CT` beside the other one is zero,
4376    // which leaves the other one: `1E¯13∨1` is 1 in GNU, not `1E¯13`.
4377    let vanishes = |v: f64, other: f64| gnu && v != 0.0 && v.abs() <= tol.ct * other.abs();
4378    zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, 0, &mut out, |a, b, slot| {
4379        let (a, b) = (whole(a), whole(b));
4380        let (a, b) = (if vanishes(a, b) { 0.0 } else { a }, if vanishes(b, a) { 0.0 } else { b });
4381        let Some(g) = gcd_decimal(a, b).or_else(|| gcd_f64(a, b, tol)) else {
4382            ok = false;
4383            return false;
4384        };
4385        *slot = if op == ScalarDyad::Gcd {
4386            g
4387        } else if g == 0.0 {
4388            0.0
4389        } else {
4390            a / g * b
4391        };
4392        true
4393    });
4394    if !ok {
4395        return Err(Error::domain("LCM/GCD needs finite values", span));
4396    }
4397    Ok(Data::F64(out.into()))
4398}
4399
4400/// LCM/GCD over two buffers. Two booleans stay boolean, where the pair is
4401/// exactly logical and (LCM) / or (GCD); integers give integers; the real
4402/// GCD of fractions runs the same Euclid on the values themselves.
4403#[allow(clippy::too_many_arguments)]
4404fn lcm_gcd_data(
4405    op: ScalarDyad,
4406    x: &Data,
4407    xoff: usize,
4408    xdiv: usize,
4409    y: &Data,
4410    yoff: usize,
4411    ydiv: usize,
4412    n: usize,
4413    tol: Tol,
4414    rules: Rules,
4415    span: Span,
4416) -> Result<Data> {
4417    // GNU APL's GCD rounds its arguments and keeps a whole one's sign
4418    // beside a zero; J's and Dyalog's do neither.
4419    let gnu = rules.lang == crate::Lang::Apl
4420        && rules.gcd_rule == crate::frontend::GcdRule::Tolerant;
4421    let t = arith_type(x.dtype(), y.dtype(), span)?;
4422    if t == DType::Complex {
4423        // The Gaussian-integer versions, which is what both references give.
4424        return complex_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n, span);
4425    }
4426    if t.is_exact()
4427        && let Some(d) = exact_dyad_data(op, t, x, xoff, xdiv, y, yoff, ydiv, n, span)?
4428    {
4429        return Ok(d);
4430    }
4431    let both_bool = x.dtype() == DType::Bool && y.dtype() == DType::Bool;
4432    let float = t == DType::F64;
4433    let (xs, ys) = if float {
4434        let (mut tx, mut ty) = (Vec::new(), Vec::new());
4435        let xf = borrow_f64(x, &mut tx);
4436        let yf = borrow_f64(y, &mut ty);
4437        let integral = |v: &[f64]| v.iter().all(|&a| a.fract() == 0.0 && fits_i64(a));
4438        if !integral(xf) || !integral(yf) {
4439            return real_lcm_gcd(op, xf, xoff, xdiv, yf, yoff, ydiv, n, tol, gnu, span);
4440        }
4441        (
4442            xf.iter().map(|&a| a as i64).collect::<Vec<_>>(),
4443            yf.iter().map(|&a| a as i64).collect::<Vec<_>>(),
4444        )
4445    } else {
4446        let (mut tx, mut ty) = (Vec::new(), Vec::new());
4447        (borrow_i64(x, &mut tx).to_vec(), borrow_i64(y, &mut ty).to_vec())
4448    };
4449    // The chunk flag carries "every value fits an i64", so the whole pass
4450    // widens to float exactly when the sequential one would.
4451    let (out, fits) = par::fill(n, |start, part: &mut [i128]| {
4452        let mut fits = true;
4453        zip_chunk(&xs, xoff, xdiv, &ys, yoff, ydiv, start, part, |a, b, slot| {
4454            let (a, b) = (a as i128, b as i128);
4455            let g = if gnu { signed_gcd_i128(a, b) } else { gcd_i128(a, b) };
4456            let v = if op == ScalarDyad::Gcd {
4457                g
4458            } else if g == 0 {
4459                0
4460            } else {
4461                a / g * b
4462            };
4463            fits &= i64::try_from(v).is_ok();
4464            *slot = v;
4465            true
4466        });
4467        fits
4468    });
4469    if !fits || float {
4470        return Ok(Data::F64(par::map(&out, |&v| v as f64).into()));
4471    }
4472    if both_bool {
4473        return Ok(Data::Bool(par::map(&out, |&v| v as u8).into()));
4474    }
4475    Ok(Data::I64(par::map(&out, |&v| v as i64).into()))
4476}
4477
4478// ------------------------------------------------------- the exact types
4479
4480/// Numeric data widened to rationals. None for a type above the exact part
4481/// of the tower, which has no exact reading.
4482fn to_rat_vec(d: &Data) -> Option<Vec<Rat>> {
4483    Some(match d {
4484        Data::Bool(v) => v.iter().map(|&b| Rat::from_int(Ext::from(b))).collect(),
4485        Data::I64(v) => v.iter().map(|&x| Rat::from_int(Ext::from(x))).collect(),
4486        Data::Ext(v) => v.iter().map(|x| Rat::from_int(x.clone())).collect(),
4487        Data::Rat(v) => v.to_vec(),
4488        Data::F64(_) | Data::Complex(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => {
4489            return None;
4490        }
4491    })
4492}
4493
4494/// The elements one pass really reads, as rationals: indices
4495/// `off .. off + (n-1)/div`, rebased to zero.
4496///
4497/// A fold hands the SAME buffer to every step with a different offset, so
4498/// converting the whole of it each time would make the fold quadratic. The
4499/// window is the whole buffer in the ordinary elementwise case, and one
4500/// element in a fold step.
4501fn rat_window(d: &Data, off: usize, div: usize, n: usize) -> Option<Vec<Rat>> {
4502    if n == 0 {
4503        return Some(Vec::new());
4504    }
4505    let end = off + (n - 1) / div + 1;
4506    if off == 0 && end == d.len() {
4507        return to_rat_vec(d);
4508    }
4509    to_rat_vec(&d.slice(off, end))
4510}
4511
4512/// A finished exact pass as data: extended when the arguments were extended
4513/// AND every answer is whole, rational otherwise.
4514///
4515/// That one rule is the whole demotion story. It makes `4x % 2` extended and
4516/// `1x % 3` rational, and it leaves `1r2 - 1r2` rational even though the
4517/// answer is zero — a rational never falls back down the tower, which is
4518/// what the reference reports of it.
4519fn exact_data(t: DType, out: Vec<Rat>) -> Data {
4520    if t == DType::Ext && out.iter().all(Rat::is_integer) {
4521        return Data::Ext(out.iter().map(|r| r.to_int().expect("whole")).collect());
4522    }
4523    Data::Rat(out.into())
4524}
4525
4526/// The complaint a power too large to hold makes.
4527fn too_large(span: Span) -> Error {
4528    Error::domain(
4529        format!(
4530            "the exact result needs more than {} bits; use floats for a value this large",
4531            exact::MAX_BITS
4532        ),
4533        span,
4534    )
4535}
4536
4537/// `a ^ b` in the exact types. None when the answer is not exact — a
4538/// fractional exponent, or zero raised to a negative one.
4539fn exact_pow(a: &Rat, b: &Rat, span: Span) -> Result<Option<Rat>> {
4540    let Some(e) = b.to_int().as_ref().and_then(exact::ext_to_i64) else {
4541        return Ok(None);
4542    };
4543    if let Some(v) = a.pow(e) {
4544        return Ok(Some(v));
4545    }
4546    // `pow` declines for two reasons; only one of them is an error.
4547    if a.is_zero() && e < 0 { Ok(None) } else { Err(too_large(span)) }
4548}
4549
4550/// One elementwise dyadic pass in the exact types. `Ok(None)` means the
4551/// operation has no exact answer for these arguments, and the caller widens
4552/// to float exactly as it would for a machine integer that overflowed.
4553#[allow(clippy::too_many_arguments)]
4554fn exact_dyad_data(
4555    op: ScalarDyad,
4556    t: DType,
4557    x: &Data,
4558    xoff: usize,
4559    xdiv: usize,
4560    y: &Data,
4561    yoff: usize,
4562    ydiv: usize,
4563    n: usize,
4564    span: Span,
4565) -> Result<Option<Data>> {
4566    use ScalarDyad::*;
4567    let (Some(xs), Some(ys)) = (rat_window(x, xoff, xdiv, n), rat_window(y, yoff, ydiv, n))
4568    else {
4569        return Ok(None);
4570    };
4571    let mut out = Vec::with_capacity(n);
4572    for i in 0..n {
4573        let a = &xs[i / xdiv];
4574        let b = &ys[i / ydiv];
4575        let v = match op {
4576            Add => a.add(b),
4577            Sub => a.sub(b),
4578            Mul => a.mul(b),
4579            // A zero divisor is an infinity, which no rational spells.
4580            DivJ | DivApl => match a.div(b) {
4581                Some(v) => v,
4582                None => return Ok(None),
4583            },
4584            Min => a.min(b).clone(),
4585            Max => a.max(b).clone(),
4586            Residue => exact::rat_residue(a, b),
4587            Gcd => exact::rat_gcd(a, b),
4588            Lcm => exact::rat_lcm(a, b),
4589            Pow => match exact_pow(a, b, span)? {
4590                Some(v) => v,
4591                None => return Ok(None),
4592            },
4593            Binomial => match (a.to_int(), b.to_int()) {
4594                (Some(k), Some(m)) => match exact::ext_binomial(&k, &m) {
4595                    Some(v) => Rat::from_int(v),
4596                    None => return Ok(None),
4597                },
4598                _ => return Ok(None),
4599            },
4600            // An exact root exists only between whole numbers: the
4601            // reference answers `3 %: 8r27` with a float, not with `2r3`.
4602            Root if t == DType::Ext => {
4603                let (Some(k), Some(m)) = (a.to_int(), b.to_int()) else {
4604                    return Ok(None);
4605                };
4606                let Some(k) = exact::ext_to_i64(&k).and_then(|k| u32::try_from(k).ok()) else {
4607                    return Ok(None);
4608                };
4609                match exact::exact_root(k, &m) {
4610                    Some(v) => Rat::from_int(v),
4611                    None => return Ok(None),
4612                }
4613            }
4614            Root | Log | Circle | MakeComplex | PolarBy => return Ok(None),
4615            // Comparisons never reach here; `compare_data` takes them.
4616            Eq | Ne | Lt | Le | Gt | Ge => return Ok(None),
4617        };
4618        out.push(v);
4619    }
4620    Ok(Some(exact_data(t, out)))
4621}
4622
4623/// Elementwise monadic application in the exact types. `Ok(None)` widens to
4624/// float, as in the dyadic pass.
4625fn exact_monad(op: ScalarMonad, y: &Array) -> Option<Array> {
4626    use ScalarMonad::*;
4627    let v = to_rat_vec(&y.data)?;
4628    let shape = y.shape.clone();
4629    // The three that answer with a whole number whatever they were given:
4630    // `<. 7r2` is the extended 3, not the rational 3.
4631    if matches!(op, Floor | Ceil | Signum) {
4632        let out: Vec<Ext> = v
4633            .iter()
4634            .map(|r| match op {
4635                Floor => r.floor(),
4636                Ceil => r.ceil(),
4637                _ => r.signum(),
4638            })
4639            .collect();
4640        return Some(Array::new(shape, Data::Ext(out.into())).with_layout(y.layout()));
4641    }
4642    let two = Rat::from_int(Ext::from(2));
4643    let mut out = Vec::with_capacity(v.len());
4644    for r in &v {
4645        let value = match op {
4646            Conj => r.clone(),
4647            Neg => r.neg(),
4648            Abs => r.abs(),
4649            Recip => r.recip()?,
4650            Inc => r.add(&Rat::one()),
4651            Dec => r.sub(&Rat::one()),
4652            OneMinus => Rat::one().sub(r),
4653            Double => r.add(r),
4654            Halve => r.div(&two).expect("two is not zero"),
4655            Square => r.mul(r),
4656            Sqrt => r.sqrt()?,
4657            Factorial => Rat::from_int(r.to_int().as_ref().and_then(exact::ext_factorial)?),
4658            // No exact answer: the transcendentals, the two that make a
4659            // complex value, and logical negation.
4660            Exp | Ln | Pi | Imaginary | Polar | Not => return None,
4661            Floor | Ceil | Signum => unreachable!("handled above"),
4662        };
4663        out.push(value);
4664    }
4665    Some(Array::new(shape, exact_data(y.dtype(), out)).with_layout(y.layout()))
4666}
4667
4668/// `x: y`: the argument in the exact types. Whole values become extended
4669/// integers; anything else becomes the simplest rational within the
4670/// dialect's comparison tolerance of it, so `x: 0.1` is `1r10` rather than
4671/// the binary fraction a double really holds.
4672fn to_exact(y: &Array, span: Span) -> Result<Array> {
4673    let data = match &y.data {
4674        Data::Ext(_) | Data::Rat(_) => return Ok(y.clone()),
4675        Data::Bool(v) => Data::Ext(v.iter().map(|&b| Ext::from(b)).collect()),
4676        Data::I64(v) => Data::Ext(v.iter().map(|&x| Ext::from(x)).collect()),
4677        Data::F64(v) => {
4678            let mut out = Vec::with_capacity(v.len());
4679            for &x in v.iter() {
4680                out.push(exact::f64_to_rat(x).ok_or_else(|| {
4681                    Error::domain("an infinity has no exact value", span)
4682                })?);
4683            }
4684            exact_data(DType::Ext, out)
4685        }
4686        Data::Complex(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => {
4687            return Err(Error::domain(
4688                format!("x: needs real numbers, not {} data", y.dtype().name()),
4689                span,
4690            ));
4691        }
4692    };
4693    Ok(Array::new(y.shape.clone(), data).with_layout(y.layout()))
4694}
4695
4696/// `_1 x: y`: an exact value back as a machine number — an extended integer
4697/// as an integer where it fits, a rational as a float.
4698fn from_exact(y: &Array) -> Array {
4699    let shape = y.shape.clone();
4700    match &y.data {
4701        Data::Ext(v) => match v.iter().map(exact::ext_to_i64).collect::<Option<Vec<i64>>>() {
4702            Some(out) => Array::new(shape, Data::I64(out.into())).with_layout(y.layout()),
4703            None => Array::new(shape, Data::F64(v.iter().map(exact::ext_to_f64).collect()))
4704                .with_layout(y.layout()),
4705        },
4706        Data::Rat(v) => Array::new(shape, Data::F64(v.iter().map(Rat::to_f64).collect()))
4707            .with_layout(y.layout()),
4708        _ => y.clone(),
4709    }
4710}
4711
4712/// `x x: y`: the exact form named by x.
4713fn exact_form(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
4714    match one_whole(x, "the form x: converts to", near, span)? {
4715        1 => {
4716            let e = to_exact(y, span)?;
4717            e.cast(DType::Rat).ok_or_else(|| Error::internal("an exact value has no rational form"))
4718        }
4719        2 => {
4720            let e = to_exact(y, span)?;
4721            let v = to_rat_vec(&e.data).ok_or_else(|| Error::internal("x: gave an inexact value"))?;
4722            let mut out = Vec::with_capacity(2 * v.len());
4723            for r in &v {
4724                out.push(r.numer().clone());
4725                out.push(r.denom().clone());
4726            }
4727            let mut shape = y.shape.clone();
4728            shape.push(2);
4729            Ok(Array::new(shape, Data::Ext(out.into())))
4730        }
4731        -1 => Ok(from_exact(y)),
4732        // The one that leaves an inexact argument alone.
4733        -2 => {
4734            if !y.dtype().is_numeric() {
4735                return Err(Error::domain(
4736                    format!("x: needs real numbers, not {} data", y.dtype().name()),
4737                    span,
4738                ));
4739            }
4740            Ok(y.clone())
4741        }
4742        n => Err(Error::domain(
4743            format!("x: converts to form 1, 2, _1 or _2, not {n}"),
4744            span,
4745        )),
4746    }
4747}
4748
4749/// Exact comparison of two exact buffers. No tolerance applies: two exact
4750/// values are equal when they are the same number, which is why
4751/// `(10x^30) = 1 + 10x^30` is 0 where the float answer would be 1.
4752#[allow(clippy::too_many_arguments)]
4753fn exact_compare_data(
4754    op: ScalarDyad,
4755    x: &Data,
4756    xoff: usize,
4757    xdiv: usize,
4758    y: &Data,
4759    yoff: usize,
4760    ydiv: usize,
4761    n: usize,
4762) -> Option<Data> {
4763    let (xs, ys) = (rat_window(x, xoff, xdiv, n)?, rat_window(y, yoff, ydiv, n)?);
4764    let out: Vec<u8> = (0..n)
4765        .map(|i| {
4766            let ord = xs[i / xdiv].cmp(&ys[i / ydiv]);
4767            cmp_result(op, Some(ord)) as u8
4768        })
4769        .collect();
4770    Some(Data::Bool(out.into()))
4771}
4772
4773/// One elementwise dyadic pass over two buffers. Element `i` of the result
4774/// pairs `x[xoff + i / xdiv]` with `y[yoff + i / ydiv]`, so broadcasting and
4775/// folding both run without materialising cells.
4776#[allow(clippy::too_many_arguments)]
4777fn scalar_dyad_data(
4778    op: ScalarDyad,
4779    x: &Data,
4780    xoff: usize,
4781    xdiv: usize,
4782    y: &Data,
4783    yoff: usize,
4784    ydiv: usize,
4785    n: usize,
4786    tol: Tol,
4787    rules: Rules,
4788    span: Span,
4789) -> Result<Data> {
4790    use ScalarDyad::*;
4791    if x.dtype() == DType::Symbol || y.dtype() == DType::Symbol {
4792        match op {
4793            // Comparison takes the path below, which knows symbols.
4794            Eq | Ne | Lt | Le | Gt | Ge => {}
4795            // `<.` and `>.` are the smaller and the larger of two names,
4796            // and a name has an order, so they answer a symbol.
4797            Min | Max => {
4798                return symbol_min_max(op, x, xoff, xdiv, y, yoff, ydiv, n, span);
4799            }
4800            _ => return Err(symbol_arith(span)),
4801        }
4802    }
4803    if matches!(op, Eq | Ne | Lt | Le | Gt | Ge) {
4804        return compare_data(op, x, xoff, xdiv, y, yoff, ydiv, n, tol, span);
4805    }
4806    if matches!(op, Lcm | Gcd) {
4807        return lcm_gcd_data(op, x, xoff, xdiv, y, yoff, ydiv, n, tol, rules, span);
4808    }
4809    let t = arith_type(x.dtype(), y.dtype(), span)?;
4810    if t.is_exact()
4811        && let Some(d) = exact_dyad_data(op, t, x, xoff, xdiv, y, yoff, ydiv, n, span)?
4812    {
4813        return Ok(d);
4814    }
4815    // No exact answer above: widen, exactly as an integer overflow does.
4816    if t == DType::I64 && !matches!(op, DivJ | DivApl | Log | Root | Circle) {
4817        // Binomial reaches this path: a whole pair has a whole answer, and
4818        // the i64 step declines (None) exactly where J widens to float.
4819        if let Some(d) = int_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n) {
4820            return Ok(d);
4821        }
4822        // Integer overflow (or a fractional result): J widens to float.
4823    }
4824    if t == DType::Complex
4825        || matches!(op, MakeComplex | PolarBy)
4826        || pass_leaves_reals(op, x, xoff, xdiv, y, yoff, ydiv, n)
4827    {
4828        let data = complex_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n, span)?;
4829        // GNU APL has no infinite logarithm in the complex domain either:
4830        // `¯1⍟0` is a DOMAIN ERROR there, exactly as `2⍟0` is on the reals,
4831        // and the real path above already refuses that one.
4832        if op == Log
4833            && rules.lang == crate::Lang::Apl
4834            && let Data::Complex(v) = &data
4835            && v.iter().any(|z| !z[0].is_finite() || !z[1].is_finite())
4836        {
4837            return Err(Error::domain("this logarithm has no value", span));
4838        }
4839        if op == Circle && circle_reads_a_part(x, xoff, xdiv, n) && let Data::Complex(v) = &data {
4840            return Ok(Data::F64(v.iter().map(|z| z[0]).collect()));
4841        }
4842        return Ok(data);
4843    }
4844    float_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n, tol, span)
4845}
4846
4847/// Elementwise dyadic application of a scalar operation to whole arrays.
4848/// Frame the results of a pervading scalar function. Cells that all came
4849/// back simple scalars make a simple array again — `(1 2)+(3 4)` is a plain
4850/// vector — and anything else is enclosed, which is what keeps the nesting.
4851fn frame_pervaded(frame: Vec<usize>, cells: Vec<Array>, span: Span) -> Result<Array> {
4852    if cells.iter().all(|c| c.rank() == 0 && c.dtype() != DType::Box) {
4853        return assemble(&frame, cells, span);
4854    }
4855    let boxes: Vec<Array> = cells.into_iter().collect();
4856    Ok(Array::new(frame, Data::Box(boxes.into())))
4857}
4858
4859/// APL's scalar functions PERVADE a nested argument: they descend through
4860/// the boxes, item by item, and apply to the simple values at the bottom.
4861/// The two sides agree by the ordinary scalar rule at every level, so a
4862/// scalar spreads over a nested array's items as it does over a simple
4863/// array's elements. J has no such rule — a box there is a type error.
4864fn pervade_dyad(
4865    op: ScalarDyad,
4866    x: &Array,
4867    y: &Array,
4868    cfg: EvalCfg,
4869    span: Span,
4870) -> Result<Array> {
4871    let p = agree(&x.shape, &y.shape, &x.shape, &y.shape, cfg.agreement, span)?;
4872    if p.n == 0 {
4873        return Ok(Array::new(p.frame, Data::empty(DType::Box)));
4874    }
4875    let (xr, yr) = (x.to_row_major(), y.to_row_major());
4876    let mut cells = Vec::with_capacity(p.n);
4877    for i in 0..p.n {
4878        let a = open_cell(&atom(&xr, i / p.x_div));
4879        let b = open_cell(&atom(&yr, i / p.y_div));
4880        cells.push(scalar_dyad(op, &a, &b, cfg, span)?);
4881    }
4882    frame_pervaded(p.frame, cells, span)
4883}
4884
4885/// The monadic half of [`pervade_dyad`].
4886fn pervade_monad(op: ScalarMonad, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
4887    if y.count() == 0 {
4888        return Ok(Array::new(y.shape.clone(), Data::empty(DType::Box)));
4889    }
4890    let yr = y.to_row_major();
4891    let mut cells = Vec::with_capacity(y.count());
4892    for i in 0..y.count() {
4893        let a = open_cell(&atom(&yr, i));
4894        cells.push(scalar_monad(op, &a, cfg, span)?);
4895    }
4896    frame_pervaded(y.shape.clone(), cells, span)
4897}
4898
4899/// The same array with its complex values read as the reals they are, or
4900/// `None` when one of them really is complex. A value that is not complex
4901/// at all needs no reading and answers for itself.
4902fn as_real(a: &Array) -> Option<Array> {
4903    if a.dtype() != DType::Complex {
4904        return Some(a.clone());
4905    }
4906    let real: Option<Vec<f64>> = a.to_f64_vec();
4907    Some(Array::new(a.shape.clone(), Data::F64(real?.into())))
4908}
4909
4910fn scalar_dyad(
4911    op: ScalarDyad,
4912    x: &Array,
4913    y: &Array,
4914    cfg: EvalCfg,
4915    span: Span,
4916) -> Result<Array> {
4917    if cfg.rules.lang == crate::Lang::Apl
4918        && (x.dtype() == DType::Box || y.dtype() == DType::Box)
4919    {
4920        return pervade_dyad(op, x, y, cfg, span);
4921    }
4922    // A complex value with no imaginary part is ordered by the real it
4923    // displays as: J answers `1 <. j. 0` with 0 and `3j0 < 4` with 1, while
4924    // `3!:0 j. 0` still reports the complex type. Only the ordering verbs
4925    // read a value that way — arithmetic keeps the complex type through its
4926    // answer, which is why the demotion sits here and not in the maker.
4927    if matches!(op, ScalarDyad::Min | ScalarDyad::Max | ScalarDyad::Lt
4928        | ScalarDyad::Le | ScalarDyad::Gt | ScalarDyad::Ge)
4929        && (x.dtype() == DType::Complex || y.dtype() == DType::Complex)
4930        && let (Some(a), Some(b)) = (as_real(x), as_real(y))
4931    {
4932        return scalar_dyad(op, &a, &b, cfg, span);
4933    }
4934    let p = agree(&x.shape, &y.shape, &x.shape, &y.shape, cfg.agreement, span)?;
4935    // Nothing to apply the verb to: `'a' + ''` is an empty, not a type
4936    // error, because no pair of elements was ever formed. The agreement
4937    // above still holds — `1 2 3 + ''` is a length error either way.
4938    if p.n == 0 {
4939        return Ok(Array::new(p.frame, Data::empty(empty_result_type(x, y))));
4940    }
4941    let data = scalar_dyad_data(
4942        op,
4943        &x.data,
4944        0,
4945        p.x_div,
4946        &y.data,
4947        0,
4948        p.y_div,
4949        p.n,
4950        cfg.tol,
4951        cfg.rules,
4952        span,
4953    )?;
4954    Ok(Array::new(p.frame, data))
4955}
4956
4957/// The element type of an empty answer. A numeric operand names it; with
4958/// none, the numbers an arithmetic result would have held.
4959fn empty_result_type(x: &Array, y: &Array) -> DType {
4960    for a in [x, y] {
4961        if a.dtype().is_numeric() {
4962            return a.dtype();
4963        }
4964    }
4965    DType::I64
4966}
4967
4968/// Is `v` exactly representable as an i64?
4969fn fits_i64(v: f64) -> bool {
4970    v.is_finite() && v >= i64::MIN as f64 && v < i64::MAX as f64
4971}
4972
4973/// Does a real argument have no real answer under this monad?
4974fn monad_leaves_reals(op: ScalarMonad, d: &Data) -> bool {
4975    use ScalarMonad::*;
4976    match op {
4977        // The two that make a complex number out of a real one.
4978        Imaginary | Polar => d.dtype().is_numeric(),
4979        Sqrt | Ln => match d {
4980            Data::I64(v) => par::any(v, |&x| x < 0),
4981            Data::F64(v) => par::any(v, |&x| x < 0.0),
4982            Data::Ext(v) => v.iter().any(|x| x.sign() == num_bigint::Sign::Minus),
4983            Data::Rat(v) => v.iter().any(|x| x < &Rat::zero()),
4984            _ => false,
4985        },
4986        _ => false,
4987    }
4988}
4989
4990/// Elementwise monadic application in the complex domain.
4991fn complex_monad(op: ScalarMonad, y: &Array, span: Span) -> Result<Array> {
4992    use ScalarMonad::*;
4993    let mut tmp = Vec::new();
4994    let v = borrow_cx(&y.data, &mut tmp);
4995    if y.count() > 0 && v.is_empty() {
4996        return Err(wrong_type(y.dtype(), span));
4997    }
4998    let data = match op {
4999        // Magnitude is the one that leaves the complex domain again.
5000        Abs => Data::F64(par::map(v, |&z| cx::abs(z)).into()),
5001        Not => return Err(Error::domain("logical negation needs values of 0 or 1", span)),
5002        Factorial => {
5003            return Err(Error::not_yet("the factorial of a complex number", span));
5004        }
5005        _ => {
5006            let step: fn(Cx) -> Cx = match op {
5007                Conj => cx::conj,
5008                Neg => cx::neg,
5009                Signum => cx::signum,
5010                Recip => cx::recip,
5011                Sqrt => cx::sqrt,
5012                Exp => cx::exp,
5013                Ln => cx::ln,
5014                Floor => cx::floor,
5015                Ceil => cx::ceil,
5016                OneMinus => |z| cx::sub(cx::ONE, z),
5017                Inc => |z| cx::add(z, cx::ONE),
5018                Dec => |z| cx::sub(z, cx::ONE),
5019                Double => |z| cx::add(z, z),
5020                Halve => |z| [z[0] / 2.0, z[1] / 2.0],
5021                Square => |z| cx::mul(z, z),
5022                Pi => |z| [std::f64::consts::PI * z[0], std::f64::consts::PI * z[1]],
5023                Imaginary => |z| cx::mul(cx::I, z),
5024                Polar => |z| cx::exp(cx::mul(cx::I, z)),
5025                Abs | Not | Factorial => unreachable!("handled above"),
5026            };
5027            Data::Complex(par::map(v, |&z| step(z)).into())
5028        }
5029    };
5030    Ok(Array::new(y.shape.clone(), data).with_layout(y.layout()))
5031}
5032
5033/// Elementwise monadic application to a whole array.
5034fn scalar_monad(op: ScalarMonad, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
5035    use ScalarMonad::*;
5036    if cfg.rules.lang == crate::Lang::Apl && y.dtype() == DType::Box {
5037        return pervade_monad(op, y, cfg, span);
5038    }
5039    let tol = cfg.tol;
5040    let d = &y.data;
5041    // An empty argument has no element for the verb to run on, so its type
5042    // never comes up: `%: ''` is an empty, not a type error.
5043    if y.count() == 0 && !d.dtype().is_numeric() {
5044        return Ok(Array::new(y.shape.clone(), Data::empty(DType::I64)));
5045    }
5046    if d.dtype() == DType::Complex || monad_leaves_reals(op, d) {
5047        return complex_monad(op, y, span);
5048    }
5049    if d.dtype().is_exact() && let Some(a) = exact_monad(op, y) {
5050        return Ok(a);
5051    }
5052    // No exact answer above: the float pass below takes over.
5053    // The float-only operations borrow float data as it lies; anything else
5054    // is widened once into `tmp` first.
5055    let mut tmp = Vec::new();
5056    let data = match op {
5057        // Conjugation is the identity on reals.
5058        Conj if d.dtype().is_numeric() => d.clone(),
5059        Conj => return Err(wrong_type(d.dtype(), span)),
5060        // Both make a complex value out of any argument, so they never
5061        // reach the real path.
5062        Imaginary | Polar => return Err(Error::internal("a complex monad on the real path")),
5063        Neg => match d {
5064            Data::Bool(v) => Data::I64(par::map(v, |&b| -(b as i64)).into()),
5065            Data::I64(v) => match par::try_map(v, i64::checked_neg) {
5066                Some(out) => Data::I64(out.into()),
5067                None => Data::F64(par::map(v, |&x| -(x as f64)).into()),
5068            },
5069            Data::F64(v) => Data::F64(par::map(v, |&x| -x).into()),
5070            _ => return Err(wrong_type(d.dtype(), span)),
5071        },
5072        Signum => match d {
5073            Data::Bool(v) => Data::I64(par::map(v, |&b| b as i64).into()),
5074            Data::I64(v) => Data::I64(par::map(v, |&x| x.signum()).into()),
5075            // NaN has no sign here; it yields 0, and so does anything the
5076            // dialect's tolerance reads as zero.
5077            Data::F64(v) => Data::F64(
5078                par::map(v, |&x| {
5079                    if tol.is_zero(x) {
5080                        0.0
5081                    } else if x > 0.0 {
5082                        1.0
5083                    } else if x < 0.0 {
5084                        -1.0
5085                    } else {
5086                        0.0
5087                    }
5088                })
5089                .into(),
5090            ),
5091            _ => return Err(wrong_type(d.dtype(), span)),
5092        },
5093        Recip => {
5094            // `% 0` is infinity in J. GNU APL has no such value: `÷0` is a
5095            // DOMAIN ERROR, as its dyadic `2÷0` already is here, and the
5096            // monad has to refuse the same pair the dyad does — including
5097            // through `¨`, `/` and `\`, which all arrive at this one step.
5098            let v = as_f64(d, &mut tmp, span)?;
5099            if !tol.is_j() && par::any(v, |&x| x == 0.0) {
5100                return Err(Error::domain("zero has no reciprocal", span));
5101            }
5102            Data::F64(par::map(v, |&x| if x == 0.0 { f64::INFINITY } else { 1.0 / x }).into())
5103        }
5104        Sqrt => {
5105            // A negative value went to the complex path before this point.
5106            let v = as_f64(d, &mut tmp, span)?;
5107            Data::F64(par::map(v, |&x| x.sqrt()).into())
5108        }
5109        Exp => {
5110            let v = as_f64(d, &mut tmp, span)?;
5111            Data::F64(par::map(v, |&x| x.exp()).into())
5112        }
5113        Abs => match d {
5114            Data::Bool(_) => d.clone(),
5115            Data::I64(v) => match par::try_map(v, i64::checked_abs) {
5116                Some(out) => Data::I64(out.into()),
5117                None => Data::F64(par::map(v, |&x| (x as f64).abs()).into()),
5118            },
5119            Data::F64(v) => Data::F64(par::map(v, |&x| x.abs()).into()),
5120            _ => return Err(wrong_type(d.dtype(), span)),
5121        },
5122        Floor | Ceil => match d {
5123            Data::Bool(v) => Data::I64(par::map(v, |&b| b as i64).into()),
5124            Data::I64(_) => d.clone(),
5125            Data::F64(v) => {
5126                let round = |x: f64| if op == Floor { tol.floor(x) } else { tol.ceil(x) };
5127                // Integer when every rounded value is one, as in J.
5128                match par::try_map(v, |x| {
5129                    let r = round(x);
5130                    fits_i64(r).then_some(r as i64)
5131                }) {
5132                    Some(out) => Data::I64(out.into()),
5133                    None => Data::F64(par::map(v, |&x| round(x)).into()),
5134                }
5135            }
5136            _ => return Err(wrong_type(d.dtype(), span)),
5137        },
5138        Inc | Dec => {
5139            let step = if op == Inc { 1i64 } else { -1 };
5140            match d {
5141                Data::Bool(v) => Data::I64(par::map(v, |&b| b as i64 + step).into()),
5142                Data::I64(v) => match par::try_map(v, |x: i64| x.checked_add(step)) {
5143                    Some(out) => Data::I64(out.into()),
5144                    None => Data::F64(par::map(v, |&x| x as f64 + step as f64).into()),
5145                },
5146                Data::F64(v) => Data::F64(par::map(v, |&x| x + step as f64).into()),
5147                _ => return Err(wrong_type(d.dtype(), span)),
5148            }
5149        }
5150        Double | Square => match d {
5151            Data::Bool(v) => {
5152                Data::I64(par::map(v, |&b| if op == Double { 2 * b as i64 } else { b as i64 }).into())
5153            }
5154            Data::I64(v) => {
5155                let f = |x: i64| if op == Double { x.checked_mul(2) } else { x.checked_mul(x) };
5156                match par::try_map(v, f) {
5157                    Some(out) => Data::I64(out.into()),
5158                    None => Data::F64(
5159                        par::map(v, |&x| {
5160                            let x = x as f64;
5161                            if op == Double { x + x } else { x * x }
5162                        })
5163                        .into(),
5164                    ),
5165                }
5166            }
5167            Data::F64(v) => {
5168                Data::F64(par::map(v, |&x| if op == Double { x + x } else { x * x }).into())
5169            }
5170            _ => return Err(wrong_type(d.dtype(), span)),
5171        },
5172        Halve => {
5173            let v = as_f64(d, &mut tmp, span)?;
5174            Data::F64(par::map(v, |&x| x / 2.0).into())
5175        }
5176        Pi => {
5177            let v = as_f64(d, &mut tmp, span)?;
5178            Data::F64(par::map(v, |&x| std::f64::consts::PI * x).into())
5179        }
5180        Factorial => {
5181            let v = as_f64(d, &mut tmp, span)?;
5182            let out = par::map(v, |&x| factorial_as(x, tol));
5183            if tol.is_j() {
5184                // The one factorial J refuses. Everything else its gamma
5185                // cannot reach it answers with `_`, which `factorial_as`
5186                // has already done.
5187                if v.iter().zip(&out).any(|(&x, &r)| tol.made_nan(r, x, 0.0)) {
5188                    return Err(Error::nan("`! __` has no value", span));
5189                }
5190            } else if par::any(&out, |v: &f64| !v.is_finite()) {
5191                // GNU APL refuses every factorial without a value: `!¯3`
5192                // and `!¯1` sit on a pole of the gamma function, `!171` has
5193                // overflowed it. J answers all three with `_`.
5194                return Err(Error::domain("this factorial has no value", span));
5195            }
5196            Data::F64(out.into())
5197        }
5198        Ln => {
5199            // As with `Sqrt`: a negative value is already on the complex path.
5200            let v = as_f64(d, &mut tmp, span)?;
5201            // ln(0) is negative infinity, which is what J prints as __. GNU
5202            // APL has no such value and refuses `⍟0`, exactly as it
5203            // refuses `÷0`.
5204            if !tol.is_j() && par::any(v, |&x| x == 0.0) {
5205                return Err(Error::domain("zero has no logarithm", span));
5206            }
5207            Data::F64(par::map(v, |&x| x.ln()).into())
5208        }
5209        OneMinus => match d {
5210            Data::Bool(v) => Data::Bool(par::map(v, |&b| 1 - b).into()),
5211            Data::I64(v) => match par::try_map(v, |x: i64| 1i64.checked_sub(x)) {
5212                Some(out) => Data::I64(out.into()),
5213                None => Data::F64(par::map(v, |&x| 1.0 - x as f64).into()),
5214            },
5215            Data::F64(v) => Data::F64(par::map(v, |&x| 1.0 - x).into()),
5216            _ => return Err(wrong_type(d.dtype(), span)),
5217        },
5218        Not => {
5219            let bad = || Error::domain("logical negation needs values of 0 or 1", span);
5220            match d {
5221                Data::Bool(v) => Data::Bool(par::map(v, |&b| 1 - b).into()),
5222                Data::I64(v) => {
5223                    let out = par::try_map(v, |x: i64| match x {
5224                        0 => Some(1u8),
5225                        1 => Some(0u8),
5226                        _ => None,
5227                    })
5228                    .ok_or_else(bad)?;
5229                    Data::Bool(out.into())
5230                }
5231                Data::F64(v) => {
5232                    let out = par::try_map(v, |x: f64| {
5233                        if x == 0.0 {
5234                            Some(1u8)
5235                        } else if x == 1.0 {
5236                            Some(0u8)
5237                        } else {
5238                            None
5239                        }
5240                    })
5241                    .ok_or_else(bad)?;
5242                    Data::Bool(out.into())
5243                }
5244                _ => return Err(bad()),
5245            }
5246        }
5247    };
5248    Ok(Array::new(y.shape.clone(), data).with_layout(y.layout()))
5249}
5250
5251// -------------------------------------------------- structural operations
5252
5253/// Reverse the axes.
5254///
5255/// Nothing moves: reversing every axis is exactly what reading the same
5256/// buffer in the other layout does, so this is a reversed shape, the same
5257/// buffer, and the flag flipped. Whatever reads the result either knows
5258/// both layouts or is handed the rows, materialised once and only if some
5259/// verb really needs them.
5260fn transpose_axes(y: &Array) -> Array {
5261    if y.rank() < 2 {
5262        return y.clone();
5263    }
5264    let out_shape: Vec<usize> = y.shape.iter().rev().copied().collect();
5265    let flipped = match y.layout() {
5266        Layout::RowMajor => Layout::ColMajor,
5267        Layout::ColMajor => Layout::RowMajor,
5268    };
5269    Array::new(out_shape, y.data.clone()).with_layout(flipped)
5270}
5271
5272/// J `i.`: an ascending sequence laid out in shape |y|, running backwards
5273/// along every axis whose given length was negative.
5274fn iota_j(y: &Array, near: NearInt, span: Span) -> Result<Array> {
5275    if y.rank() > 1 {
5276        return Err(Error::new(
5277            ErrorKind::Rank,
5278            "index generator needs a scalar or vector argument",
5279            Some(span),
5280        ));
5281    }
5282    let dims = y
5283        .to_i64_vec_near(near)
5284        .ok_or_else(|| Error::domain("index generator needs integer lengths", span))?;
5285    let shape: Vec<usize> = dims.iter().map(|d| d.unsigned_abs() as usize).collect();
5286    let n = crate::limits::elements(&shape, span)?;
5287    let st = strides(&shape);
5288    let mut out = Vec::with_capacity(n);
5289    let mut coord = vec![0usize; shape.len()];
5290    for _ in 0..n {
5291        let mut v = 0usize;
5292        for k in 0..shape.len() {
5293            let c = if dims[k] < 0 { shape[k] - 1 - coord[k] } else { coord[k] };
5294            v += c * st[k];
5295        }
5296        out.push(v as i64);
5297        odometer(&mut coord, &shape);
5298    }
5299    let data = Data::I64(out.into());
5300    // An extended length generates extended indices, so `*/ >: i. 25x` is
5301    // the exact factorial rather than the overflowing machine one.
5302    let data = if y.dtype() == DType::Ext {
5303        data.cast(DType::Ext).ok_or_else(|| Error::internal("integers have no extended form"))?
5304    } else {
5305        data
5306    };
5307    Ok(Array::new(shape, data))
5308}
5309
5310/// The first item, or a cell of fills when there are no items.
5311fn head(y: &Array) -> Array {
5312    if y.rank() == 0 {
5313        return y.clone();
5314    }
5315    if y.items() == 0 {
5316        let cell_shape = y.shape[1..].to_vec();
5317        let n: usize = cell_shape.iter().product();
5318        return Array::new(cell_shape, fill_data(y.dtype(), n));
5319    }
5320    y.item(0)
5321}
5322
5323fn behead(y: &Array, span: Span) -> Result<Array> {
5324    if y.rank() == 0 {
5325        return Err(Error::domain("cannot drop the first item of a scalar", span));
5326    }
5327    if y.items() == 0 {
5328        return Ok(y.clone());
5329    }
5330    let m = y.item_size();
5331    let mut shape = y.shape.clone();
5332    shape[0] -= 1;
5333    Ok(Array::new(shape, y.data.slice(m, y.count())))
5334}
5335
5336/// The last item, or a cell of fills when there are no items.
5337fn tail(y: &Array) -> Array {
5338    if y.rank() == 0 {
5339        return y.clone();
5340    }
5341    let n = y.items();
5342    if n == 0 {
5343        let cell_shape = y.shape[1..].to_vec();
5344        let m: usize = cell_shape.iter().product();
5345        return Array::new(cell_shape, fill_data(y.dtype(), m));
5346    }
5347    y.item(n - 1)
5348}
5349
5350/// All items but the last. A scalar has one item, so it curtails to empty.
5351fn curtail(y: &Array) -> Array {
5352    if y.rank() == 0 {
5353        return Array::empty(y.dtype());
5354    }
5355    let n = y.items();
5356    if n == 0 {
5357        return y.clone();
5358    }
5359    let m = y.item_size();
5360    let mut shape = y.shape.clone();
5361    shape[0] = n - 1;
5362    Array::new(shape, y.data.slice(0, (n - 1) * m))
5363}
5364
5365/// Reverse the items (the leading axis).
5366fn reverse(y: &Array) -> Array {
5367    if y.rank() == 0 {
5368        return y.clone();
5369    }
5370    let n = y.items();
5371    let m = y.item_size();
5372    let mut data = Data::empty(y.dtype());
5373    for i in (0..n).rev() {
5374        for k in 0..m {
5375            push_elem(&mut data, &y.data, i * m + k);
5376        }
5377    }
5378    Array::new(y.shape.clone(), data)
5379}
5380
5381/// `x |. y`: rotate axis k of y left by `x[k]`, cyclically; a negative
5382/// amount rotates right. A scalar argument has nothing to rotate.
5383fn rotate(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
5384    let counts = axis_counts(x, "rotate", near, span)?;
5385    if y.rank() == 0 {
5386        return Ok(y.clone());
5387    }
5388    if counts.len() > y.rank() {
5389        return Err(Error::new(
5390            ErrorKind::Length,
5391            format!(
5392                "rotate has {} amounts for an argument of rank {}",
5393                counts.len(),
5394                y.rank()
5395            ),
5396            Some(span),
5397        ));
5398    }
5399    let st = strides(&y.shape);
5400    let n = y.count();
5401    let r = y.rank();
5402    let mut data = Data::empty(y.dtype());
5403    let mut coord = vec![0usize; r];
5404    for _ in 0..n {
5405        let mut idx = 0usize;
5406        for k in 0..r {
5407            // No axis is empty here: an empty axis makes n zero.
5408            let len = y.shape[k] as i64;
5409            // The amount is reduced modulo the axis BEFORE the coordinate
5410            // is added to it: a rotate of 9223372036854775806 is a legal
5411            // sentence, and adding it to a coordinate first overflows.
5412            let s = counts.get(k).copied().unwrap_or(0).rem_euclid(len);
5413            idx += (coord[k] as i64 + s).rem_euclid(len) as usize * st[k];
5414        }
5415        push_elem(&mut data, &y.data, idx);
5416        odometer(&mut coord, &y.shape);
5417    }
5418    Ok(Array::new(y.shape.clone(), data))
5419}
5420
5421/// `x ⌽ y` and `x ⊖ y`: rotate one axis of y, by one amount per vector
5422/// along it.
5423///
5424/// APL's left argument is not J's one amount per axis. Exactly one axis
5425/// moves — the last for `⌽`, the leading one for `⊖`, the named one for
5426/// `⌽[k]` — and x holds one amount for each vector along it, so `⍴x` must
5427/// be `⍴y` with that axis removed. A scalar (or a one-item vector, which
5428/// GNU APL accepts as one) rotates every vector by the same amount.
5429/// Anything else is a conformability error: a rank error where the ranks
5430/// disagree and a length error where only the lengths do.
5431fn rotate_apl(x: &Array, y: &Array, last: bool, near: NearInt, span: Span) -> Result<Array> {
5432    let scalar_like = x.rank() == 0 || (x.rank() == 1 && x.count() == 1);
5433    // A scalar has no axis to rotate, so it is its own answer — but only
5434    // for a left argument that could have rotated something.
5435    if y.rank() == 0 {
5436        return if scalar_like {
5437            Ok(y.clone())
5438        } else {
5439            Err(Error::new(
5440                ErrorKind::Rank,
5441                format!(
5442                    "rotate has a rank-{} left argument for a scalar, which needs a scalar",
5443                    x.rank()
5444                ),
5445                Some(span),
5446            ))
5447        };
5448    }
5449    let axis = if last { y.rank() - 1 } else { 0 };
5450    let want: Vec<usize> =
5451        y.shape.iter().enumerate().filter(|&(k, _)| k != axis).map(|(_, &n)| n).collect();
5452    if !scalar_like {
5453        if x.rank() != want.len() {
5454            return Err(Error::new(
5455                ErrorKind::Rank,
5456                format!(
5457                    "rotate has a rank-{} left argument for axis {axis} of {}, which needs rank {}",
5458                    x.rank(),
5459                    show_shape(&y.shape),
5460                    want.len()
5461                ),
5462                Some(span),
5463            ));
5464        }
5465        if x.shape != want {
5466            return Err(Error::new(
5467                ErrorKind::Length,
5468                format!(
5469                    "rotate has a {} left argument for axis {axis} of {}, which needs {}",
5470                    show_shape(&x.shape),
5471                    show_shape(&y.shape),
5472                    show_shape(&want)
5473                ),
5474                Some(span),
5475            ));
5476        }
5477    }
5478    let counts = x
5479        .to_i64_vec_near(near)
5480        .ok_or_else(|| Error::domain("rotate needs integer lengths", span))?;
5481    let len = y.shape[axis] as i64;
5482    let n = y.count();
5483    if n == 0 {
5484        return Ok(y.clone());
5485    }
5486    let st = strides(&y.shape);
5487    let r = y.rank();
5488    let mut data = Data::empty(y.dtype());
5489    let mut coord = vec![0usize; r];
5490    for _ in 0..n {
5491        // Which vector this element sits on, in the order x holds them.
5492        let mut which = 0usize;
5493        for (k, &c) in coord.iter().enumerate() {
5494            if k != axis {
5495                which = which * y.shape[k] + c;
5496            }
5497        }
5498        let s = if scalar_like { counts[0] } else { counts[which] };
5499        // Reduced modulo the axis before the coordinate joins it: the
5500        // amount may be any i64 the program can write.
5501        let s = s.rem_euclid(len);
5502        let mut idx = 0usize;
5503        for (k, &c) in coord.iter().enumerate() {
5504            let c = if k == axis { (c as i64 + s).rem_euclid(len) as usize } else { c };
5505            idx += c * st[k];
5506        }
5507        push_elem(&mut data, &y.data, idx);
5508        odometer(&mut coord, &y.shape);
5509    }
5510    Ok(Array::new(y.shape.clone(), data))
5511}
5512
5513/// A key identifying one element exactly, for equality by hashing. Only
5514/// comparable within one dtype; the two zeros share a key.
5515fn elem_key(d: &Data, i: usize) -> u64 {
5516    match d {
5517        Data::Bool(v) => v[i] as u64,
5518        Data::I64(v) => v[i] as u64,
5519        Data::F64(v) => {
5520            let x = v[i];
5521            if x == 0.0 { 0 } else { x.to_bits() }
5522        }
5523        Data::Complex(v) => cx_key(v[i]),
5524        Data::Char(v) => v[i] as u64,
5525        // A symbol IS its table index, so the index is the key.
5526        Data::Symbol(v) => v[i] as u64,
5527        // Neither a box nor an exact value has a cheap key; their callers
5528        // compare them by content.
5529        Data::Ext(_) | Data::Rat(_) | Data::Box(_) => 0,
5530    }
5531}
5532
5533/// A key comparable across the numeric dtypes: numbers by their float value,
5534/// characters by codepoint. Callers keep the two kinds apart.
5535fn num_key(d: &Data, i: usize) -> u64 {
5536    match d {
5537        Data::Bool(v) => (v[i] as f64).to_bits(),
5538        Data::I64(v) => (v[i] as f64).to_bits(),
5539        Data::F64(v) => {
5540            let x = v[i];
5541            if x == 0.0 { 0.0f64.to_bits() } else { x.to_bits() }
5542        }
5543        Data::Complex(v) => cx_key(v[i]),
5544        Data::Char(v) => v[i] as u64,
5545        Data::Symbol(v) => v[i] as u64,
5546        // As in `elem_key`: never reached for boxed or exact data.
5547        Data::Ext(_) | Data::Rat(_) | Data::Box(_) => 0,
5548    }
5549}
5550
5551/// One key for a complex value; the two parts have to disagree to disagree.
5552fn cx_key(z: Cx) -> u64 {
5553    let bits = |x: f64| if x == 0.0 { 0u64 } else { x.to_bits() };
5554    bits(z[0]) ^ bits(z[1]).rotate_left(32)
5555}
5556
5557/// Distinct items, in the order of their first occurrence.
5558fn nub(y: &Array, tol: Tol) -> Array {
5559    if y.rank() == 0 {
5560        return Array::new(vec![1], y.data.clone());
5561    }
5562    let n = y.items();
5563    let m = y.item_size();
5564    let mut keep = Vec::new();
5565    if y.dtype() == DType::Box || y.dtype().is_exact() {
5566        // Boxed and exact items are compared by content, one against the
5567        // ones kept so far: there is no key to hash.
5568        for i in 0..n {
5569            if !keep.iter().any(|&j| arrays_match(&y.item(i), &y.item(j), tol)) {
5570                keep.push(i);
5571            }
5572        }
5573    } else if y.dtype() == DType::F64 && tol.ct != 0.0 {
5574        // Tolerant equality is not an equivalence a hash can stand in for:
5575        // each float item is compared against the ones already kept.
5576        let mut tv = Vec::new();
5577        let v = borrow_f64(&y.data, &mut tv);
5578        for i in 0..n {
5579            if !keep.iter().any(|&j| (0..m).all(|k| tol.eq(v[i * m + k], v[j * m + k]))) {
5580                keep.push(i);
5581            }
5582        }
5583    } else {
5584        let mut seen: HashSet<Vec<u64>> = HashSet::with_capacity(n);
5585        for i in 0..n {
5586            let key: Vec<u64> = (0..m).map(|k| elem_key(&y.data, i * m + k)).collect();
5587            if seen.insert(key) {
5588                keep.push(i);
5589            }
5590        }
5591    }
5592    let mut data = Data::empty(y.dtype());
5593    for &i in &keep {
5594        for k in 0..m {
5595            push_elem(&mut data, &y.data, i * m + k);
5596        }
5597    }
5598    let mut shape = y.shape.clone();
5599    shape[0] = keep.len();
5600    Array::new(shape, data)
5601}
5602
5603/// Which ordering a grade puts whole arrays in when its items are boxed —
5604/// J's total array ordering, or the APL2 rule GNU APL implements. The two
5605/// disagree at every step, so a comparison says which one it is answering
5606/// for.
5607#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5608enum Tao {
5609    J,
5610    Apl2,
5611    /// Dyalog's total array ordering.
5612    Dyalog,
5613}
5614
5615impl Tao {
5616    fn of(rules: Rules) -> Tao {
5617        match rules.lang {
5618            crate::Lang::J => Tao::J,
5619            crate::Lang::Apl => match rules.nested_grade {
5620                NestedGrade::Apl2 => Tao::Apl2,
5621                NestedGrade::TotalOrder => Tao::Dyalog,
5622            },
5623        }
5624    }
5625
5626    /// The type class compared before the atoms: J puts numeric first,
5627    /// then symbol, then character, then boxed; APL2 puts character first,
5628    /// then numeric, then nested. APL has no symbols of its own, so a
5629    /// symbol that reaches an APL grade sorts with the characters it is
5630    /// made of names of.
5631    fn class(self, dt: DType) -> u8 {
5632        match self {
5633            Tao::J => match dt {
5634                DType::Symbol => 1,
5635                DType::Char => 2,
5636                DType::Box => 3,
5637                _ => 0,
5638            },
5639            Tao::Apl2 => match dt {
5640                DType::Char | DType::Symbol => 0,
5641                DType::Box => 2,
5642                _ => 1,
5643            },
5644            // Dyalog puts every number before every character. A nested
5645            // value is never placed by its own type here: an array with
5646            // atoms is decided by them, and an atomless one by the item it
5647            // would have held (`proto_item`), so the box arm is reached
5648            // only for an empty that has forgotten its prototype.
5649            Tao::Dyalog => match dt {
5650                DType::Char | DType::Symbol => 2,
5651                DType::Box => 1,
5652                _ => 0,
5653            },
5654        }
5655    }
5656}
5657
5658/// A grade's comparator: which total ordering it puts whole arrays in, and
5659/// the tolerance the numbers inside it are read with.
5660///
5661/// APL's `⍋` and `⍒` compare under `⎕CT` — `⍋1.0000000000001 1` is `1 2` in
5662/// GNU APL, the two keys equal and left in the order they came — while J's
5663/// grade is exact whatever the comparison tolerance is: jconsole answers
5664/// `/: 1 1.0000000000001 1` with `0 2 1`.
5665#[derive(Clone, Copy, Debug)]
5666struct Grading {
5667    tao: Tao,
5668    tol: Tol,
5669}
5670
5671impl Grading {
5672    fn of(rules: Rules, tol: Tol) -> Grading {
5673        let tao = Tao::of(rules);
5674        // J's grade is exact, and so is Dyalog's: `⍋2 (1+1E¯14) 1` is
5675        // `3 2 1` there, the two near-equal keys separated rather than
5676        // tied. Only the APL2 line reads `⎕CT` here.
5677        let exact = tao == Tao::J || tao == Tao::Dyalog;
5678        Grading { tao, tol: if exact { Tol { ct: 0.0, ..tol } } else { tol } }
5679    }
5680
5681    fn class(self, dt: DType) -> u8 {
5682        self.tao.class(dt)
5683    }
5684}
5685
5686/// Order two whole arrays, which is how a grade compares boxed items.
5687///
5688/// J compares the type class first — and an EMPTY array has no atoms to
5689/// take a class from, so it takes the lowest one whatever its type, which
5690/// is why `/: (<''),(<<1)` puts the empty character list first and two
5691/// empties of different types tie. Then the rank, then the shape read with
5692/// the LAST axis most significant, then the atoms in row-major order.
5693///
5694/// APL2 compares the rank first, then the shape read from the FIRST axis,
5695/// then the atoms, where a character precedes a number precedes a nested
5696/// value; two arrays with no atoms are separated by their types instead.
5697///
5698/// Both are exact — a grade never reads the comparison tolerance — and a
5699/// NaN ties with everything, which keeps the sort total.
5700fn cmp_items_total(x: &Array, y: &Array, ord: Grading) -> std::cmp::Ordering {
5701    use std::cmp::Ordering::Equal;
5702    match ord.tao {
5703        Tao::Dyalog => cmp_items_dyalog(x, y, ord),
5704        Tao::J => {
5705            let class = |a: &Array| if a.count() == 0 { 0 } else { ord.class(a.dtype()) };
5706            class(x)
5707                .cmp(&class(y))
5708                .then_with(|| x.rank().cmp(&y.rank()))
5709                .then_with(|| x.shape.iter().rev().cmp(y.shape.iter().rev()))
5710                .then_with(|| cmp_atoms(x, y, ord))
5711        }
5712        Tao::Apl2 => x
5713            .rank()
5714            .cmp(&y.rank())
5715            .then_with(|| x.shape.iter().cmp(y.shape.iter()))
5716            .then_with(|| cmp_atoms(x, y, ord))
5717            .then_with(|| {
5718                if x.count() == 0 {
5719                    ord.class(x.dtype()).cmp(&ord.class(y.dtype()))
5720                } else {
5721                    Equal
5722                }
5723            }),
5724    }
5725}
5726
5727/// Two whole arrays in Dyalog's total array ordering.
5728///
5729/// The shapes are brought together rather than compared: the lower rank
5730/// gains leading 1s, and each axis is taken to the longer of the two, so
5731/// the arrays are read position by position over the shape that covers
5732/// both. A position one array has and the other does not answers at once —
5733/// what is not there sorts below every value there is — and a position
5734/// both hold compares its atoms, which recurses where an atom is nested.
5735/// Only arrays with no atoms to separate them reach the type (numbers,
5736/// then nested values, then characters) and then the shape, which is read
5737/// with the LAST axis most significant.
5738///
5739/// Derived from the recorded Dyalog answers in
5740/// `crates/libjay/tests/snapshots/apl/grade.snap`, which is what pins it.
5741fn cmp_items_dyalog(x: &Array, y: &Array, ord: Grading) -> std::cmp::Ordering {
5742    use std::cmp::Ordering::{Equal, Greater, Less};
5743    // Two simple scalars are the bottom of the recursion; everything else
5744    // is read as an array of atoms.
5745    if x.rank() == 0 && y.rank() == 0 && x.dtype() != DType::Box && y.dtype() != DType::Box {
5746        return cmp_atoms(x, y, ord);
5747    }
5748    let rank = x.rank().max(y.rank());
5749    let extend = |a: &Array| -> Vec<usize> {
5750        let mut s = vec![1usize; rank - a.rank()];
5751        s.extend_from_slice(&a.shape);
5752        s
5753    };
5754    let (sx, sy) = (extend(x), extend(y));
5755    let common: Vec<usize> = (0..rank).map(|k| sx[k].max(sy[k])).collect();
5756    let (xr, yr) = (x.to_row_major(), y.to_row_major());
5757    let (dx, dy) = (xr.row_major_data(), yr.row_major_data());
5758    let (stx, sty) = (strides(&sx), strides(&sy));
5759    let mut order = Equal;
5760    if !common.contains(&0) {
5761        let mut coord = vec![0usize; rank];
5762        loop {
5763            let inside = |s: &[usize]| (0..rank).all(|k| coord[k] < s[k]);
5764            let at = |st: &[usize]| -> usize { (0..rank).map(|k| coord[k] * st[k]).sum() };
5765            let here = match (inside(&sx), inside(&sy)) {
5766                (true, true) => {
5767                    cmp_items_dyalog(&atom_array(dx, at(&stx)), &atom_array(dy, at(&sty)), ord)
5768                }
5769                // What is not there is below what is.
5770                (true, false) => Greater,
5771                (false, true) => Less,
5772                (false, false) => Equal,
5773            };
5774            if here != Equal {
5775                order = here;
5776                break;
5777            }
5778            // The odometer wraps to all zeros when the last position is
5779            // done, and every position either decides or holds equal
5780            // atoms, so this walks no further than the shorter array.
5781            odometer(&mut coord, &common);
5782            if coord.iter().all(|&c| c == 0) {
5783                break;
5784            }
5785        }
5786    }
5787    if order != Equal {
5788        return order;
5789    }
5790    // Nothing was there to compare, so the arrays are separated by the item
5791    // they WOULD have held and then by their shape, last axis first.
5792    match (proto_item(x), proto_item(y)) {
5793        // The prototypes are values like any other, and are compared under
5794        // the same tolerance the atoms would have been.
5795        (Some(px), Some(py)) => cmp_items_dyalog(&px, &py, ord),
5796        _ => ord.class(x.dtype()).cmp(&ord.class(y.dtype())),
5797    }
5798    .then_with(|| x.shape.iter().rev().cmp(y.shape.iter().rev()))
5799}
5800
5801/// The item an atomless array would have held, as an array of its own: a
5802/// nested empty's remembered prototype, and for a simple one the fill its
5803/// type implies — a zero, or a blank. `None` where there is nothing to say,
5804/// which is a nested empty that has forgotten (and an array with atoms,
5805/// which is never separated this way).
5806fn proto_item(a: &Array) -> Option<Array> {
5807    if let Some(p) = a.proto() {
5808        return Some(p.clone());
5809    }
5810    match a.dtype() {
5811        DType::Box => None,
5812        dt => Some(Array::new(vec![], fill_data(dt, 1))),
5813    }
5814}
5815
5816/// Element `i` of a buffer as an array of its own: a box gives up its
5817/// contents, anything else is a simple scalar.
5818fn atom_array(d: &Data, i: usize) -> Array {
5819    match d {
5820        Data::Box(v) => v[i].clone(),
5821        _ => {
5822            let mut one = Data::empty(d.dtype());
5823            push_elem(&mut one, d, i);
5824            Array::new(vec![], one)
5825        }
5826    }
5827}
5828
5829/// The atoms of two arrays of the same shape, in row-major order. A boxed
5830/// atom is compared by its contents, which is where the ordering recurses.
5831fn cmp_atoms(x: &Array, y: &Array, ord: Grading) -> std::cmp::Ordering {
5832    use std::cmp::Ordering::Equal;
5833    let n = x.count();
5834    if n == 0 {
5835        return Equal;
5836    }
5837    let (xr, yr) = (x.to_row_major(), y.to_row_major());
5838    let (dx, dy) = (xr.row_major_data(), yr.row_major_data());
5839    if matches!(dx, Data::Box(_)) || matches!(dy, Data::Box(_)) {
5840        return (0..n)
5841            .map(|i| cmp_items_total(&atom_array(dx, i), &atom_array(dy, i), ord))
5842            .find(|o| *o != Equal)
5843            .unwrap_or(Equal);
5844    }
5845    // Neither side is boxed, so one class covers all of each side's atoms.
5846    let classes = ord.class(dx.dtype()).cmp(&ord.class(dy.dtype()));
5847    if classes != Equal {
5848        return classes;
5849    }
5850    match (dx, dy) {
5851        (Data::Char(a), Data::Char(b)) => a[..n].cmp(&b[..n]),
5852        _ => cmp_numbers(dx, dy, n, ord.tol),
5853    }
5854}
5855
5856/// Two numeric buffers, `n` elements each, compared in order. The widening
5857/// is the one `arrays_match` uses, so `1r2` and `0.5` compare where they
5858/// belong however each is spelled.
5859fn cmp_numbers(dx: &Data, dy: &Data, n: usize, tol: Tol) -> std::cmp::Ordering {
5860    use std::cmp::Ordering::Equal;
5861    let seek = |f: &dyn Fn(usize) -> std::cmp::Ordering| {
5862        (0..n).map(f).find(|o| *o != Equal).unwrap_or(Equal)
5863    };
5864    match DType::promote(dx.dtype(), dy.dtype()) {
5865        Some(DType::Complex) => {
5866            let (mut ta, mut tb) = (Vec::new(), Vec::new());
5867            let (a, b) = (borrow_cx(dx, &mut ta), borrow_cx(dy, &mut tb));
5868            seek(&|k| tol_ord(a[k][0], b[k][0], tol).then_with(|| tol_ord(a[k][1], b[k][1], tol)))
5869        }
5870        Some(DType::F64) => {
5871            let (mut ta, mut tb) = (Vec::new(), Vec::new());
5872            let (a, b) = (borrow_f64(dx, &mut ta), borrow_f64(dy, &mut tb));
5873            seek(&|k| tol_ord(a[k], b[k], tol))
5874        }
5875        Some(t) if t.is_exact() => match (to_rat_vec(dx), to_rat_vec(dy)) {
5876            (Some(a), Some(b)) => seek(&|k| a[k].cmp(&b[k])),
5877            _ => Equal,
5878        },
5879        // Characters and boxes never reach here: the classes agreed.
5880        None => Equal,
5881        Some(_) => {
5882            let (mut ta, mut tb) = (Vec::new(), Vec::new());
5883            let (a, b) = (borrow_i64(dx, &mut ta), borrow_i64(dy, &mut tb));
5884            seek(&|k| a[k].cmp(&b[k]))
5885        }
5886    }
5887}
5888
5889/// Compare items `i` and `j` (of `m` elements each) elementwise, left to
5890/// right. Characters order by codepoint; a NaN compares equal to anything,
5891/// which keeps the sort total.
5892fn cmp_items(d: &Data, i: usize, j: usize, m: usize, ord: Grading) -> std::cmp::Ordering {
5893    use std::cmp::Ordering::Equal;
5894    let (a, b) = (i * m, j * m);
5895    let ord = |k: usize| match d {
5896        Data::Bool(v) => v[a + k].cmp(&v[b + k]),
5897        Data::I64(v) => v[a + k].cmp(&v[b + k]),
5898        Data::F64(v) => tol_ord(v[a + k], v[b + k], ord.tol),
5899        // Grading a complex array orders it by real part then imaginary,
5900        // which is the order J's `/:` puts it in and the dialect's
5901        // `ComplexOrder::RealThenImaginary`; `check_gradable` has already
5902        // refused the other reading. The ordering VERBS still refuse
5903        // complex outright: a grade is a permutation, not a claim about
5904        // size.
5905        Data::Complex(v) => tol_ord(v[a + k][0], v[b + k][0], ord.tol)
5906            .then_with(|| tol_ord(v[a + k][1], v[b + k][1], ord.tol)),
5907        Data::Char(v) => v[a + k].cmp(&v[b + k]),
5908        // Symbols order by the NAME behind the index, not by the order
5909        // the two names happened to be interned in.
5910        Data::Symbol(v) => crate::symbol::cmp(v[a + k], v[b + k]),
5911        // The exact types order by value, however they are spelled: `2r4`
5912        // grades exactly where `1r2` does.
5913        Data::Ext(v) => v[a + k].cmp(&v[b + k]),
5914        Data::Rat(v) => v[a + k].cmp(&v[b + k]),
5915        // A boxed element is a whole array: the ordering of the language
5916        // being graded in decides between two of them.
5917        Data::Box(v) => cmp_items_total(&v[a + k], &v[b + k], ord),
5918    };
5919    (0..m).map(ord).find(|o| *o != Equal).unwrap_or(Equal)
5920}
5921
5922/// The stable permutation that sorts the items of `y`.
5923fn grade_order(y: &Array, down: bool, ord: Grading) -> Vec<usize> {
5924    if y.rank() == 0 {
5925        return vec![0];
5926    }
5927    let n = y.items();
5928    let m = y.item_size();
5929    let mut idx: Vec<usize> = (0..n).collect();
5930    // A stable sort leaves equal items in their original order, which is
5931    // what both languages promise, ascending and descending alike.
5932    if down {
5933        idx.sort_by(|&a, &b| cmp_items(&y.data, b, a, m, ord));
5934    } else {
5935        idx.sort_by(|&a, &b| cmp_items(&y.data, a, b, m, ord));
5936    }
5937    idx
5938}
5939
5940/// `x ⍋ y` and `x ⍒ y`: every character of y is keyed by where it first
5941/// occurs in the collating array x — the coordinate read with the LAST axis
5942/// most significant, and one past the end for a character x does not hold —
5943/// and the items of y are ordered by those keys read left to right.
5944fn collate_grade(x: &Array, y: &Array, down: bool, origin: i64, span: Span) -> Result<Array> {
5945    let chars_of = |a: &Array| -> Result<Vec<char>> {
5946        match a.row_major_data() {
5947            Data::Char(v) => Ok(v.as_slice().to_vec()),
5948            _ => Err(Error::domain("a collating grade takes characters", span)),
5949        }
5950    };
5951    let (xs, ys) = (chars_of(x)?, chars_of(y)?);
5952    let xshape = if x.rank() == 0 { vec![1] } else { x.shape.clone() };
5953    let width = xshape.len();
5954    // The key of a character: its first coordinate in x, reversed so the
5955    // last axis decides first. A character x does not hold sorts after
5956    // every one it does.
5957    let absent: Vec<usize> = xshape.iter().rev().copied().collect();
5958    let mut keys: std::collections::HashMap<char, Vec<usize>> =
5959        std::collections::HashMap::new();
5960    let xst = strides(&xshape);
5961    for (i, &c) in xs.iter().enumerate() {
5962        keys.entry(c).or_insert_with(|| {
5963            (0..width).map(|a| (i / xst[a]) % xshape[a]).rev().collect()
5964        });
5965    }
5966    let key_of = |c: char| keys.get(&c).unwrap_or(&absent).clone();
5967    let n = if y.rank() == 0 { 1 } else { y.items() };
5968    let m = if n == 0 { 0 } else { ys.len() / n };
5969    let item_keys: Vec<Vec<usize>> = (0..n)
5970        .map(|i| ys[i * m..(i + 1) * m].iter().flat_map(|&c| key_of(c)).collect())
5971        .collect();
5972    let mut idx: Vec<usize> = (0..n).collect();
5973    if down {
5974        idx.sort_by(|&a, &b| item_keys[b].cmp(&item_keys[a]));
5975    } else {
5976        idx.sort_by(|&a, &b| item_keys[a].cmp(&item_keys[b]));
5977    }
5978    Ok(Array::from_i64(idx.into_iter().map(|i| origin + i as i64).collect()))
5979}
5980
5981/// `5!:1 <'name'`: the atomic representation of what the name stands for.
5982/// A verb answers with the representation of the verb, a value with the
5983/// noun pair; either way the answer is boxed, as the reference has it.
5984fn atomic_rep(y: &Array, ctx: &Ctx<'_>, span: Span) -> Result<Array> {
5985    let name = match y.as_boxes() {
5986        Some([b]) if y.rank() == 0 => crate::gerund::text_of(b),
5987        _ => None,
5988    };
5989    let Some(name) = name else {
5990        return Err(Error::domain("5!:1 takes a boxed name", span));
5991    };
5992    if let Some(v) = ctx.env.verb(&name) {
5993        let ar = crate::gerund::verb_ar(v).ok_or_else(|| {
5994            Error::not_yet(
5995                format!("the atomic representation of {}", v.name()),
5996                span,
5997            )
5998        })?;
5999        return Ok(Array::boxed(ar.to_array()));
6000    }
6001    match ctx.env.get(&name) {
6002        Some(a) => Ok(Array::boxed(crate::gerund::Ar::Noun(a).to_array())),
6003        None => Err(Error::new(
6004            ErrorKind::Value,
6005            format!("undefined name: {name}"),
6006            Some(span),
6007        )),
6008    }
6009}
6010
6011/// `{ y`: the catalogue — every way of taking one element from each item
6012/// of y. The shapes of the items, opened, make the result's shape, and each
6013/// element of it is the boxed vector of one choice from each.
6014fn catalogue(y: &Array, span: Span) -> Result<Array> {
6015    let items = if y.rank() == 0 { vec![y.clone()] } else { y.cells(1) };
6016    // A boxed item stands for its contents; a simple one for itself.
6017    let opened: Vec<Array> = items
6018        .iter()
6019        .map(|it| match it.as_boxes() {
6020            Some(bs) if it.rank() == 0 => bs[0].clone(),
6021            _ => it.clone(),
6022        })
6023        .collect();
6024    let mut shape: Vec<usize> = Vec::new();
6025    for o in &opened {
6026        shape.extend_from_slice(&o.shape);
6027    }
6028    let total: usize = shape.iter().product();
6029    let mut out = Vec::with_capacity(total);
6030    let mut coord = vec![0usize; shape.len()];
6031    for _ in 0..total {
6032        let mut at = 0usize;
6033        let mut picks = Vec::with_capacity(opened.len());
6034        for o in &opened {
6035            let st = strides(&o.shape);
6036            let idx: usize = (0..o.rank()).map(|a| coord[at + a] * st[a]).sum();
6037            at += o.rank();
6038            let mut data = Data::empty(o.dtype());
6039            push_elem(&mut data, o.row_major_data(), idx);
6040            picks.push(Array::new(vec![], data));
6041        }
6042        out.push(assemble(&[picks.len()], picks, span)?);
6043        odometer(&mut coord, &shape);
6044    }
6045    Ok(Array::new(shape, Data::Box(out.into())))
6046}
6047
6048/// `e. y`: for every element of y, which items of the raze of y it holds —
6049/// so the answer is shaped `($y), #items of the raze`.
6050fn raze_in(y: &Array, tol: Tol, span: Span) -> Result<Array> {
6051    let all = raze(y, span)?;
6052    let n = if all.rank() == 0 { 1 } else { all.items() };
6053    let elements: Vec<Array> = (0..y.count())
6054        .map(|i| {
6055            let mut data = Data::empty(y.dtype());
6056            push_elem(&mut data, y.row_major_data(), i);
6057            let one = Array::new(vec![], data);
6058            match one.as_boxes() {
6059                Some(bs) => bs[0].clone(),
6060                None => one,
6061            }
6062        })
6063        .collect();
6064    let mut out = Vec::with_capacity(elements.len() * n);
6065    for e in &elements {
6066        let row = member_j(&all, e, tol);
6067        out.extend_from_slice(row.to_i64_vec().unwrap_or_default().as_slice());
6068    }
6069    let mut shape = y.shape.clone();
6070    shape.push(n);
6071    Ok(Array::new(shape, Data::Bool(out.into_iter().map(|v| v as u8).collect::<Vec<u8>>().into())))
6072}
6073
6074/// Select items of `y` in the given order.
6075fn select_items(y: &Array, order: &[usize]) -> Array {
6076    let m = y.item_size();
6077    let mut data = Data::empty(y.dtype());
6078    for &i in order {
6079        for k in 0..m {
6080            push_elem(&mut data, &y.data, i * m + k);
6081        }
6082    }
6083    let mut shape = y.shape.clone();
6084    shape[0] = order.len();
6085    Array::new(shape, data)
6086}
6087
6088/// What a grade refuses, and the dialect setting it reads.
6089///
6090/// A grade has to be total over complex values, and the dialect says in
6091/// which order; only one of the two readings is implemented.
6092fn check_gradable(y: &Array, rules: Rules, span: Span) -> Result<()> {
6093    if y.dtype() == DType::Complex && rules.complex_order != ComplexOrder::RealThenImaginary {
6094        return Err(Error::not_yet("grading complex values by magnitude and angle", span));
6095    }
6096    Ok(())
6097}
6098
6099/// `x /: y` is `(/: y) { x`: the grade of y is an index into x, so the two
6100/// lengths need not agree — a shorter key selects fewer items, and only an
6101/// index past the end of x is an error.
6102fn grade_select(
6103    x: &Array,
6104    y: &Array,
6105    down: bool,
6106    rules: Rules,
6107    tol: Tol,
6108    span: Span,
6109) -> Result<Array> {
6110    check_gradable(y, rules, span)?;
6111    let order = grade_order(y, down, Grading::of(rules, tol));
6112    // An atom is ONE item, so the only index it answers is the first: J
6113    // reads `5 /: 1` as 5 and refuses `5 /: 1 2 3`, where a lenient reading
6114    // would hand the atom back for any key at all.
6115    if x.rank() == 0 {
6116        if let Some(&past) = order.iter().find(|&&i| i > 0) {
6117            return Err(Error::domain(
6118                format!("index {past} is out of range: the argument has 1 item"),
6119                span,
6120            ));
6121        }
6122        // Selecting that one item as many times as the grade asks: no key
6123        // at all answers the empty, which is what `0.5 /: i.0` is.
6124        return Ok(select_items(&as_list(x), &order));
6125    }
6126    if let Some(&past) = order.iter().find(|&&i| i >= x.items()) {
6127        return Err(Error::domain(
6128            format!("index {past} is out of range: the argument has {} items", x.items()),
6129            span,
6130        ));
6131    }
6132    Ok(select_items(x, &order))
6133}
6134
6135/// Whole-array equality: same shape and same values. Characters never equal
6136/// numbers; `1` equals `1.0`; NaN equals nothing.
6137pub(crate) fn arrays_match(x: &Array, y: &Array, tol: Tol) -> bool {
6138    if x.shape != y.shape {
6139        return false;
6140    }
6141    // The comparison is element against element in buffer order, so two
6142    // values laid out differently are compared in the one order.
6143    if x.layout() != y.layout() {
6144        return arrays_match(&x.to_row_major(), &y.to_row_major(), tol);
6145    }
6146    // Two empty arrays of the same shape match whatever their types are,
6147    // which is what both references answer for `'' -: i. 0`.
6148    if x.count() == 0 {
6149        return true;
6150    }
6151    if let (Data::Box(a), Data::Box(b)) = (&x.data, &y.data) {
6152        return a.iter().zip(b.iter()).all(|(p, q)| arrays_match(p, q, tol));
6153    }
6154    let (dx, dy) = (x.dtype(), y.dtype());
6155    match DType::promote(dx, dy) {
6156        None => false,
6157        Some(DType::Char) => match (&x.data, &y.data) {
6158            (Data::Char(a), Data::Char(b)) => a.as_slice() == b.as_slice(),
6159            _ => false,
6160        },
6161        // Two symbols are the same symbol exactly when they carry the same
6162        // table index, which is the whole point of interning them.
6163        Some(DType::Symbol) => match (&x.data, &y.data) {
6164            (Data::Symbol(a), Data::Symbol(b)) => a.as_slice() == b.as_slice(),
6165            _ => false,
6166        },
6167        Some(DType::F64) => {
6168            let (mut ta, mut tb) = (Vec::new(), Vec::new());
6169            let a = borrow_f64(&x.data, &mut ta);
6170            let b = borrow_f64(&y.data, &mut tb);
6171            a.iter().zip(b).all(|(p, q)| tol.eq(*p, *q))
6172        }
6173        Some(DType::Complex) => {
6174            let (mut ta, mut tb) = (Vec::new(), Vec::new());
6175            let a = borrow_cx(&x.data, &mut ta);
6176            let b = borrow_cx(&y.data, &mut tb);
6177            a.iter().zip(b).all(|(p, q)| tol.eq_cx(*p, *q))
6178        }
6179        Some(t) if t.is_exact() => match (to_rat_vec(&x.data), to_rat_vec(&y.data)) {
6180            (Some(a), Some(b)) => a == b,
6181            _ => false,
6182        },
6183        Some(_) => {
6184            let (mut ta, mut tb) = (Vec::new(), Vec::new());
6185            let a = borrow_i64(&x.data, &mut ta);
6186            let b = borrow_i64(&y.data, &mut tb);
6187            a.iter().zip(b).all(|(p, q)| p == q)
6188        }
6189    }
6190}
6191
6192/// Item `i` of `a`, treating a scalar as an array of one item.
6193fn item_or_self(a: &Array, i: usize) -> Array {
6194    if a.rank() == 0 { a.clone() } else { a.item(i) }
6195}
6196
6197/// `x e. y`: for every cell of x shaped like an item of y, is it an item
6198/// of y? A cell of the wrong shape simply is not one, as in J.
6199fn member_j(x: &Array, y: &Array, tol: Tol) -> Array {
6200    let cell_rank = y.rank().saturating_sub(1).min(x.rank());
6201    let frame_rank = x.rank() - cell_rank;
6202    let frame: Vec<usize> = x.shape[..frame_rank].to_vec();
6203    let nf: usize = frame.iter().product();
6204    let items = y.items();
6205    let mut out = Vec::with_capacity(nf);
6206    for i in 0..nf {
6207        let cell = x.cell_at(frame_rank, i);
6208        out.push((0..items).any(|j| arrays_match(&cell, &item_or_self(y, j), tol)) as u8);
6209    }
6210    Array::new(frame, Data::Bool(out.into()))
6211}
6212
6213/// `x ∊ y`: for every element of x, does that value occur anywhere in y?
6214fn member_apl(x: &Array, y: &Array, tol: Tol) -> Array {
6215    let n = x.count();
6216    if x.dtype() == DType::Box
6217        || y.dtype() == DType::Box
6218        || x.dtype().is_exact()
6219        || y.dtype().is_exact()
6220    {
6221        // A box's elements are whole arrays and an exact value has no cheap
6222        // key, so both are compared by content; a box never equals a plain
6223        // number or character.
6224        // `⊂5` is `5` in APL, so a box holding a simple scalar compares as
6225        // that scalar: `1 2 3 ∊ (1 2)(3)` finds the 3.
6226        let opened = |a: &Array, i: usize| -> Array {
6227            let e = atom(a, i);
6228            match e.as_boxes() {
6229                Some([b]) if b.rank() == 0 && b.dtype() != DType::Box => b.clone(),
6230                _ => e,
6231            }
6232        };
6233        let out: Vec<u8> = (0..n)
6234            .map(|i| {
6235                let e = opened(x, i);
6236                u8::from((0..y.count()).any(|j| arrays_match(&e, &opened(y, j), tol)))
6237            })
6238            .collect();
6239        return Array::new(x.shape.clone(), Data::Bool(out.into()));
6240    }
6241    if x.dtype() != y.dtype()
6242        && [x.dtype(), y.dtype()].iter().any(|&d| matches!(d, DType::Char | DType::Symbol))
6243    {
6244        return Array::new(x.shape.clone(), Data::Bool(vec![0u8; n].into()));
6245    }
6246    if tol.ct != 0.0
6247        && (x.dtype() == DType::F64 || y.dtype() == DType::F64)
6248        && x.dtype() != DType::Char
6249    {
6250        // Tolerance rules a hash out; the values are compared directly.
6251        let (mut tx, mut ty) = (Vec::new(), Vec::new());
6252        let xs = borrow_f64(&x.data, &mut tx);
6253        let ys = borrow_f64(&y.data, &mut ty);
6254        let out: Vec<u8> =
6255            xs.iter().map(|a| ys.iter().any(|b| tol.eq(*a, *b)) as u8).collect();
6256        return Array::new(x.shape.clone(), Data::Bool(out.into()));
6257    }
6258    let seen: HashSet<u64> = (0..y.count()).map(|i| num_key(&y.data, i)).collect();
6259    let out: Vec<u8> =
6260        (0..n).map(|i| seen.contains(&num_key(&x.data, i)) as u8).collect();
6261    Array::new(x.shape.clone(), Data::Bool(out.into()))
6262}
6263
6264/// `x i. y` / `x ⍳ y`: where each cell of y sits among the items of x.
6265///
6266/// `vector_left` is the Dyalog reading, where the lookup table is a vector
6267/// and nothing else; without it the items of a left argument of any rank
6268/// are searched, which is what J and the APL2 line do.
6269fn index_of(
6270    x: &Array,
6271    y: &Array,
6272    origin: i64,
6273    vector_left: bool,
6274    tol: Tol,
6275    span: Span,
6276) -> Result<Array> {
6277    if vector_left && x.rank() != 1 {
6278        return Err(Error::new(
6279            ErrorKind::Rank,
6280            format!("⍳ looks up in a vector, and its left argument has rank {}", x.rank()),
6281            Some(span),
6282        ));
6283    }
6284    let cell_rank = x.rank().saturating_sub(1).min(y.rank());
6285    let frame_rank = y.rank() - cell_rank;
6286    let frame: Vec<usize> = y.shape[..frame_rank].to_vec();
6287    let nf: usize = frame.iter().product();
6288    let items = x.items();
6289    let mut out = Vec::with_capacity(nf);
6290    for i in 0..nf {
6291        let cell = y.cell_at(frame_rank, i);
6292        let at = (0..items)
6293            .find(|&j| arrays_match(&cell, &item_or_self(x, j), tol))
6294            .unwrap_or(items);
6295        out.push(origin + at as i64);
6296    }
6297    Ok(Array::new(frame, Data::I64(out.into())))
6298}
6299
6300/// `x { y` for one index atom: the rank machinery supplies the framing.
6301fn from_index(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
6302    // A boxed index is J's index specification, which reaches several axes
6303    // at once; a plain one selects an item.
6304    if let Some(spec) = x.as_boxes().and_then(<[Array]>::first) {
6305        let spec = index_spec(spec, y, near, span)?;
6306        return Ok(select_spec(&spec, y));
6307    }
6308    let idx = x
6309        .to_i64_vec_near(near)
6310        .ok_or_else(|| Error::domain("index must be an integer", span))?;
6311    let Some(&i) = idx.first() else {
6312        return Err(Error::internal("from_index with no index"));
6313    };
6314    let n = y.items() as i64;
6315    let k = if i < 0 { i + n } else { i };
6316    if k < 0 || k >= n {
6317        return Err(Error::domain(
6318            format!("index {i} is out of range: the argument has {n} items"),
6319            span,
6320        ));
6321    }
6322    Ok(item_or_self(y, k as usize))
6323}
6324
6325/// Bring `a` up to `rank` axes for catenation along `axis`. A scalar spreads
6326/// over one cross section of the other argument; one missing axis becomes a
6327/// length-1 axis at `axis`.
6328fn cat_promote(
6329    a: &Array,
6330    other: &Array,
6331    rank: usize,
6332    axis: usize,
6333    deep: bool,
6334    span: Span,
6335) -> Result<Array> {
6336    if a.rank() == rank {
6337        return Ok(a.clone());
6338    }
6339    if a.rank() == 0 {
6340        let mut shape =
6341            if other.rank() == rank { other.shape.clone() } else { vec![1usize; rank] };
6342        shape[axis] = 1;
6343        let n: usize = shape.iter().product();
6344        let mut data = Data::empty(a.dtype());
6345        for _ in 0..n {
6346            push_elem(&mut data, &a.data, 0);
6347        }
6348        return Ok(Array::new(shape, data));
6349    }
6350    // One axis short, the value is one item of the answer. J's `,` goes on
6351    // taking a wider gap the same way — `1 2 3 , (2 1 3$1)` is a rank-3
6352    // answer whose first item is the vector, filled out to the item shape —
6353    // while APL holds the two ranks to within one of each other.
6354    if a.rank() + 1 == rank || (deep && a.rank() < rank) {
6355        let mut shape = a.shape.clone();
6356        for _ in a.rank()..rank {
6357            shape.insert(axis, 1);
6358        }
6359        return Ok(Array::new(shape, a.data.clone()));
6360    }
6361    Err(Error::new(
6362        ErrorKind::Rank,
6363        format!("cannot catenate rank {} with rank {}", a.rank(), other.rank()),
6364        Some(span),
6365    ))
6366}
6367
6368/// The type two arrays that share none take when at least one of them holds
6369/// no elements.
6370///
6371/// J lets an empty operand join anything: `(0$'a') , 1 2 3` is `1 2 3`, and
6372/// an empty box vanishes beside characters the same way, because no element
6373/// of the empty side ever becomes an element of the result. Where both
6374/// sides are empty the wider container wins — a box over a character, a
6375/// character over a number.
6376fn empty_type(x: &Array, y: &Array) -> Option<DType> {
6377    match (x.count() == 0, y.count() == 0) {
6378        (true, false) => Some(y.dtype()),
6379        (false, true) => Some(x.dtype()),
6380        (true, true) => Some(match (x.dtype(), y.dtype()) {
6381            (DType::Box, _) | (_, DType::Box) => DType::Box,
6382            (DType::Char, _) | (_, DType::Char) => DType::Char,
6383            (a, b) => DType::promote(a, b)?,
6384        }),
6385        (false, false) => None,
6386    }
6387}
6388
6389/// Catenate along the leading or the last axis.
6390pub(crate) fn catenate(
6391    x: &Array,
6392    y: &Array,
6393    leading: bool,
6394    fill: bool,
6395    span: Span,
6396) -> Result<Array> {
6397    let rank = x.rank().max(y.rank()).max(1);
6398    let axis = if leading { 0 } else { rank - 1 };
6399    let deep = fill && leading;
6400    let xa = cat_promote(x, y, rank, axis, deep, span)?;
6401    let ya = cat_promote(y, x, rank, axis, deep, span)?;
6402    // J lets an operand with no elements join anything, taking the other
6403    // side's type instead of clashing with it: `(0$'a') , 1 2 3` is
6404    // `1 2 3`. The retyping happens here, before any fill is worked out, so
6405    // that the fill an unequal axis needs is the RESULT's — the empty
6406    // planes of `(2 0 3$0) , 'hello'` come out as spaces, not as zeros.
6407    let (xa, ya) = match empty_type(&xa, &ya)
6408        .filter(|_| fill && DType::promote(xa.dtype(), ya.dtype()).is_none())
6409    {
6410        None => (xa, ya),
6411        Some(dt) => {
6412            let retype = |a: Array| {
6413                if a.count() == 0 && a.dtype() != dt {
6414                    Array::new(a.shape.clone(), Data::empty(dt))
6415                } else {
6416                    a
6417                }
6418            };
6419            (retype(xa), retype(ya))
6420        }
6421    };
6422    // Axes other than the one being joined must agree. J overtakes both
6423    // sides to the larger length, which fills; APL insists they conform,
6424    // and the reference refuses the ragged case outright.
6425    let mut ragged = false;
6426    let want: Vec<i64> = (0..rank)
6427        .map(|k| {
6428            ragged |= k != axis && xa.shape[k] != ya.shape[k];
6429            xa.shape[k].max(ya.shape[k]) as i64
6430        })
6431        .collect();
6432    if ragged && !fill {
6433        return Err(Error::new(
6434            ErrorKind::Length,
6435            format!(
6436                "cannot catenate: left shape {}, right shape {}",
6437                show_shape(&xa.shape),
6438                show_shape(&ya.shape)
6439            ),
6440            Some(span),
6441        ));
6442    }
6443    let (xa, ya) = if ragged {
6444        let fit = |a: &Array| -> Result<Array> {
6445            let mut to = want.clone();
6446            to[axis] = a.shape[axis] as i64;
6447            // The lengths are ours, not the program's: no float
6448            // reaches the near-integer admission on this path.
6449            take(&Array::from_i64(to), a, false, false, NearInt::J, span)
6450        };
6451        (fit(&xa)?, fit(&ya)?)
6452    } else {
6453        (xa, ya)
6454    };
6455    // APL2 catenates a nested array to a simple one by enclosing the
6456    // simple side's items: `(1 2),⊂3 4` is a three-item nested vector. J
6457    // refuses the mixture, and its `fill` rule is what tells them apart.
6458    let (xa, ya) = if !fill && (xa.dtype() == DType::Box) != (ya.dtype() == DType::Box) {
6459        (nest_like(&xa, &ya), nest_like(&ya, &xa))
6460    } else {
6461        (xa, ya)
6462    };
6463    // And where two SIMPLE arrays share no type, APL builds a mixed simple
6464    // one rather than refusing: `1 2,'ab'` is a four-element vector of two
6465    // numbers and two characters, depth 1. J has no such value.
6466    let mixing = !fill
6467        && xa.dtype() != DType::Box
6468        && ya.dtype() != DType::Box
6469        && DType::promote(xa.dtype(), ya.dtype()).is_none();
6470    let (xa, ya) =
6471        if mixing { (spread_scalars(&xa), spread_scalars(&ya)) } else { (xa, ya) };
6472    let dt = DType::promote(xa.dtype(), ya.dtype())
6473        .ok_or_else(|| {
6474            let boxed = xa.dtype() == DType::Box || ya.dtype() == DType::Box;
6475            let what = if boxed {
6476                "cannot catenate boxed and unboxed data; box the other side first"
6477            } else {
6478                "cannot catenate character and numeric data"
6479            };
6480            Error::new(ErrorKind::Type, what, Some(span))
6481        })?;
6482    let widen = |a: &Array| -> Result<Data> {
6483        if a.dtype() == dt {
6484            Ok(a.data.clone())
6485        } else if a.count() == 0 {
6486            // An empty side brings no element to convert, so it takes the
6487            // result's type outright — there is no character to read as a
6488            // number, which is the conversion that has no meaning.
6489            Ok(Data::empty(dt))
6490        } else {
6491            a.data.cast(dt).ok_or_else(|| Error::internal("unsupported widening in catenate"))
6492        }
6493    };
6494    let xd = widen(&xa)?;
6495    let yd = widen(&ya)?;
6496    let outer: usize = xa.shape[..axis].iter().product();
6497    let ix: usize = xa.shape[axis..].iter().product();
6498    let iy: usize = ya.shape[axis..].iter().product();
6499    let mut data = Data::empty(dt);
6500    for o in 0..outer {
6501        for k in 0..ix {
6502            push_elem(&mut data, &xd, o * ix + k);
6503        }
6504        for k in 0..iy {
6505            push_elem(&mut data, &yd, o * iy + k);
6506        }
6507    }
6508    let mut shape = xa.shape.clone();
6509    shape[axis] = xa.shape[axis] + ya.shape[axis];
6510    Ok(Array::new(shape, data))
6511}
6512
6513/// `x # y` / `x / y`: item i of y appears x[i] times.
6514///
6515/// A scalar x applies to every item, and a SCALAR y is extended to as many
6516/// items as x has counts — a one-item vector is not, which is why
6517/// `1 0 1 # 5` is `5 5` and `1 0 1 # ,5` is a length error. A negative
6518/// count is APL's: it contributes that many fills. J has no such reading
6519/// and refuses it.
6520fn copy_items(x: &Array, y: &Array, apl: bool, near: NearInt, span: Span) -> Result<Array> {
6521    let counts = x
6522        .to_i64_vec_near(near)
6523        .ok_or_else(|| Error::domain("replication counts must be integers", span))?;
6524    if !apl && counts.iter().any(|&c| c < 0) {
6525        return Err(Error::domain("replication counts must be nonnegative", span));
6526    }
6527    // A scalar right argument stands in for every count, and in APL so does
6528    // an argument of ONE item along the axis: `2 0 1/,5` is `5 5 5`, where
6529    // J's `#` calls the same pair a length error.
6530    let one_item = apl && x.rank() > 0 && y.rank() > 0 && y.items() == 1 && counts.len() != 1;
6531    let scalar_y = y.rank() == 0 || one_item;
6532    let m = y.item_size();
6533    let n = if x.rank() == 0 || !scalar_y { y.items() } else { counts.len() };
6534    let per = if x.rank() == 0 { vec![counts[0]; n] } else { counts };
6535    if per.len() != n {
6536        return Err(Error::new(
6537            ErrorKind::Length,
6538            format!("{} replication count(s) for {n} item(s)", per.len()),
6539            Some(span),
6540        ));
6541    }
6542    // Items, not elements: an item of zero elements still costs a trip
6543    // round the loop, so the ceiling applies to whichever is larger.
6544    let items: u128 = per.iter().map(|&c| c.unsigned_abs() as u128).sum();
6545    let total = crate::limits::count(items * m.max(1) as u128, span)? / m.max(1);
6546    let fill = if apl { prototype_of(y) } else { None };
6547    let mut data = Data::empty(y.dtype());
6548    for (i, &c) in per.iter().enumerate() {
6549        // A scalar y stands in for every count.
6550        let src = if scalar_y { 0 } else { i };
6551        for _ in 0..c.unsigned_abs() {
6552            for k in 0..m {
6553                if c < 0 {
6554                    push_gap(&mut data, &fill);
6555                } else {
6556                    push_elem(&mut data, &y.data, src * m + k);
6557                }
6558            }
6559        }
6560    }
6561    // A scalar argument has one item, so replicating it yields a vector; an
6562    // extended one-item argument keeps the shape it already had.
6563    let mut shape = if y.rank() == 0 { vec![1] } else { y.shape.clone() };
6564    shape[0] = total;
6565    Ok(keep_proto(Array::new(shape, data), y, apl))
6566}
6567
6568/// `": y` / `⍕ y`: the argument as the characters that display it.
6569///
6570/// Characters are already their own display, so they pass through unchanged.
6571/// Anything else is laid out exactly as the session would print it: a rank-0
6572/// or rank-1 argument gives one character vector, and a higher-rank one gives
6573/// the display's lines as the rows of a character array of the same rank —
6574/// column widths span the whole argument, so every line has one width and the
6575/// planes stay aligned with each other.
6576fn format_chars(y: &Array, opts: &FmtOpts) -> Array {
6577    // A sparse array's display is a table of lines whatever its own rank
6578    // is: one line per stored entry.
6579    if y.is_sparse() {
6580        let text = crate::fmt::format_array(y, opts);
6581        let lines: Vec<&str> = text.lines().collect();
6582        let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
6583        let mut chars: Vec<char> = Vec::with_capacity(lines.len() * width);
6584        for line in &lines {
6585            chars.extend(line.chars());
6586            chars.resize(chars.len() + width - line.chars().count(), ' ');
6587        }
6588        return Array::new(vec![lines.len(), width], Data::Char(chars.into()));
6589    }
6590    if y.dtype() == DType::Char {
6591        return y.clone();
6592    }
6593    // An empty argument has nothing to lay out; J keeps its shape.
6594    if y.count() == 0 {
6595        return Array::new(y.shape.clone(), Data::empty(DType::Char));
6596    }
6597    let text = crate::fmt::format_array(y, opts);
6598    if y.dtype() == DType::Box {
6599        // A fenced box (J) takes several lines per row of cells, so the
6600        // display's own rows and columns become the last two axes of the
6601        // result. A spaced one (APL) still prints one line per row, and
6602        // keeps the plain rule below.
6603        let lines = text.lines().filter(|l| !l.is_empty()).count();
6604        let rows: usize =
6605            if y.rank() == 0 { 1 } else { y.shape[..y.rank() - 1].iter().product() };
6606        if lines != rows {
6607            return text_planes(&text, &y.shape[..y.rank().saturating_sub(2)]);
6608        }
6609    }
6610    if y.rank() < 2 {
6611        let chars: Vec<char> = text.chars().collect();
6612        return Array::new(vec![chars.len()], Data::Char(chars.into()));
6613    }
6614    // The blank lines are the plane separators, which the array does not
6615    // carry: its own shape already says where the planes are.
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 mut chars: Vec<char> = Vec::with_capacity(lines.len() * width);
6619    for line in &lines {
6620        chars.extend(line.chars());
6621        chars.resize(chars.len() + width - line.chars().count(), ' ');
6622    }
6623    // One line per row of the display: the argument's shape with its last
6624    // axis replaced by the line width.
6625    let mut shape = y.shape[..y.rank() - 1].to_vec();
6626    shape.push(width);
6627    debug_assert_eq!(lines.len(), shape[..shape.len() - 1].iter().product::<usize>());
6628    Array::new(shape, Data::Char(chars.into()))
6629}
6630
6631/// A multi-line display as a character array: the frame, then the lines of
6632/// one plane, then their common width.
6633fn text_planes(text: &str, frame: &[usize]) -> Array {
6634    let lines: Vec<&str> = text.lines().filter(|l| !l.is_empty()).collect();
6635    let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
6636    let planes: usize = frame.iter().product::<usize>().max(1);
6637    let per = lines.len() / planes;
6638    let mut chars: Vec<char> = Vec::with_capacity(lines.len() * width);
6639    for line in &lines {
6640        chars.extend(line.chars());
6641        chars.resize(chars.len() + width - line.chars().count(), ' ');
6642    }
6643    let mut shape = frame.to_vec();
6644    shape.push(per);
6645    shape.push(width);
6646    Array::new(shape, Data::Char(chars.into()))
6647}
6648
6649/// Numeric data as f64, refusing characters.
6650fn digits_of(a: &Array, what: &str, span: Span) -> Result<Vec<f64>> {
6651    a.to_f64_vec().ok_or_else(|| Error::domain(format!("{what} needs numeric data"), span))
6652}
6653
6654/// Narrow a finished digit or value buffer back to integers when the inputs
6655/// were whole and nothing left the exact range, which is what both languages
6656/// do with integer arguments.
6657fn narrow(values: Vec<f64>, integral: bool) -> Data {
6658    if integral && values.iter().all(|&v| v.fract() == 0.0 && fits_i64(v)) {
6659        return Data::I64(values.iter().map(|&v| v as i64).collect::<Vec<_>>().into());
6660    }
6661    Data::F64(values.into())
6662}
6663
6664/// True when the array holds whole numbers only.
6665fn is_integral(a: &Array) -> bool {
6666    !matches!(a.dtype(), DType::F64 | DType::Rat | DType::Char | DType::Symbol)
6667}
6668
6669/// The decode of exact digits in exact radices, accumulated in the exact
6670/// types. Whole numbers keep every digit — a 19-digit integer decoded
6671/// through f64 loses its last two — and rational digits give a rational
6672/// answer, which is what J reports for `#. 1r2 1r3`. `None` hands the pass
6673/// back to the float path, which also reports the length errors.
6674fn decode_exact(x: Option<&Array>, y: &Array) -> Option<Array> {
6675    let yr = y.to_row_major();
6676    let digits = to_rat_vec(&yr.data)?;
6677    let two = Rat::from_int(Ext::from(2));
6678    let mut digits = digits;
6679    let radix: Vec<Rat> = match x {
6680        None => vec![two; digits.len()],
6681        Some(x) => {
6682            let r = to_rat_vec(&x.to_row_major().data)?;
6683            // An ATOM of digits is the digit in every position: J reads
6684            // `2 7 1 8 #. 123x` as four 123s. A one-item LIST is not an
6685            // atom and does not spread, which is why `1 2 3 #. ,5` is a
6686            // length error where `1 2 3 #. 5` is 50.
6687            if y.rank() == 0 && r.len() != 1 {
6688                digits = vec![digits[0].clone(); r.len()];
6689            }
6690            match r.len() {
6691                1 => vec![r[0].clone(); digits.len()],
6692                n if n == digits.len() => r,
6693                _ => return None,
6694            }
6695        }
6696    };
6697    let mut acc = Rat::from_int(Ext::from(0));
6698    for (d, b) in digits.iter().zip(&radix) {
6699        acc = acc.mul(b).add(d);
6700    }
6701    let exact_in = |a: &Array| matches!(a.dtype(), DType::Ext | DType::Rat);
6702    if exact_in(y) || x.is_some_and(exact_in) {
6703        return Some(Array::new(Vec::new(), exact_data(DType::Ext, vec![acc])));
6704    }
6705    // Plain integers in, a plain integer out — but only while it fits; the
6706    // float path widens beyond that, as both references do.
6707    let whole = acc.to_int()?;
6708    Some(Array::scalar_i64(exact::ext_to_i64(&whole)?))
6709}
6710
6711/// `x #. y` / `x ⊥ y`: the digits y read in the radices x. A scalar x is the
6712/// radix of every position; otherwise the two have the same length.
6713fn decode(x: Option<&Array>, y: &Array, tol: Tol, span: Span) -> Result<Array> {
6714    if let Some(exact) = decode_exact(x, y) {
6715        return Ok(exact);
6716    }
6717    let mut digits = digits_of(y, "decode", span)?;
6718    let radix: Vec<f64> = match x {
6719        None => vec![2.0; digits.len()],
6720        Some(x) => {
6721            let r = digits_of(x, "decode", span)?;
6722            // An atom of digits fills every position the radices name;
6723            // `(i. 0) #. 5` is the empty sum, 0.
6724            if y.rank() == 0 && r.len() != 1 {
6725                digits = vec![digits[0]; r.len()];
6726            }
6727            match r.len() {
6728                1 => vec![r[0]; digits.len()],
6729                n if n == digits.len() => r,
6730                n => {
6731                    return Err(Error::new(
6732                        ErrorKind::Length,
6733                        format!("{n} radices for {} digits", digits.len()),
6734                        Some(span),
6735                    ));
6736                }
6737            }
6738        }
6739    };
6740    let mut acc = 0.0f64;
6741    for (d, b) in digits.iter().zip(&radix) {
6742        // The dialect's product, so that an infinite radix meets the same
6743        // zero-factor rule `*` does: `_ #. 2` is 2, because the running
6744        // total is still zero when the infinity multiplies it.
6745        acc = tol.mul(acc, *b) + d;
6746    }
6747    let integral = is_integral(y) && x.is_none_or(is_integral);
6748    Ok(Array::new(vec![], narrow(vec![acc], integral)))
6749}
6750
6751/// `x ⊥ y` on arguments of rank 2 and above: the inner product `+.×` over
6752/// the LAST axis of x and the LEADING axis of y. A scalar x is the radix
6753/// for every digit, as it is for a vector argument.
6754fn decode_apl(x: &Array, y: &Array, span: Span) -> Result<Array> {
6755    // With no digit to weigh, no radix is ever read and none is refused:
6756    // `'a'⊥(0⍴0)` is the empty sum, 0. The zeros stand in for a radix list
6757    // the loop below never reaches.
6758    let empty = y.count() == 0;
6759    let mut digits = if empty { Vec::new() } else { digits_of(y, "decode", span)? };
6760    let radices = if empty { vec![0.0; x.count()] } else { digits_of(x, "decode", span)? };
6761    // The digit axis is y's leading one; a scalar y has one digit. The
6762    // frames are the counts of the axes the digit axis leaves over, and a
6763    // count is a product of axis lengths rather than a division: an axis of
6764    // length zero on either side leaves no elements to divide by.
6765    let mut k = if y.rank() == 0 { 1 } else { y.shape[0] };
6766    let mut n: usize = if y.rank() == 0 { 1 } else { y.shape[1..].iter().product() };
6767    let (rows, width) = match x.rank() {
6768        0 => (1usize, 0usize),
6769        r => (x.shape[..r - 1].iter().product(), x.shape[r - 1]),
6770    };
6771    // A SINGLE digit stands in every position the radices name, whatever
6772    // rank it is written at: `1 2 3⊥5`, `1 2 3⊥,5` and `1 2 3⊥1 1⍴5` are
6773    // all 50. That is APL2's single extension, and it is why only a digit
6774    // axis of some OTHER length is a length error.
6775    if y.count() == 1 && width > 1 && width != k {
6776        digits = vec![digits[0]; width];
6777        k = width;
6778        n = 1;
6779    }
6780    // A single radix spreads the same way (`(,2)⊥1 2 3` is 11), and an
6781    // empty axis on either side weighs nothing at all: the answer is the
6782    // empty sum, which is what `1 2⊥''` and `(⍳0)⊥5` both report.
6783    if width > 1 && k != 0 && width != k {
6784        return Err(Error::new(
6785            ErrorKind::Length,
6786            format!("{width} radices for {k} digits"),
6787            Some(span),
6788        ));
6789    }
6790    // A radix axis of length zero weighs nothing: every answer is the empty
6791    // sum, whatever the digits are. Only a SCALAR x spreads its one radix
6792    // over all k digits.
6793    let per_row = if x.rank() > 0 && width == 0 { 0 } else { k };
6794    let mut out = vec![0.0f64; rows * n];
6795    for i in 0..rows {
6796        for j in 0..n {
6797            let mut acc = 0.0f64;
6798            for d in 0..per_row {
6799                let b = if width <= 1 { radices[i * width] } else { radices[i * width + d] };
6800                acc = acc * b + digits[d * n + j];
6801            }
6802            out[i * n + j] = acc;
6803        }
6804    }
6805    let mut shape: Vec<usize> = if x.rank() == 0 {
6806        Vec::new()
6807    } else {
6808        x.shape[..x.rank() - 1].to_vec()
6809    };
6810    if y.rank() > 0 {
6811        shape.extend_from_slice(&y.shape[1..]);
6812    }
6813    // A radix that was never read says nothing about the answer's type: the
6814    // empty sum is the integer 0 whatever the radix was written as.
6815    let integral = is_integral(y) && (empty || is_integral(x));
6816    Ok(Array::new(shape, narrow(out, integral)))
6817}
6818
6819/// `x ⊤ y` where x has rank 2 or more: x's LEADING axis is the radix and
6820/// its remaining axes frame the answer, so the result is shaped `(⍴x), ⍴y`.
6821fn encode_apl(x: &Array, y: &Array, tol: Tol, span: Span) -> Result<Array> {
6822    // With no value to write, no radix is ever divided by and none is
6823    // refused: `'a'⊤(0⍴0)` is the empty, shaped `(⍴x),⍴y`.
6824    let empty = y.count() == 0;
6825    let radices = if empty { vec![0.0; x.count()] } else { digits_of(x, "encode", span)? };
6826    let values = if empty { Vec::new() } else { digits_of(y, "encode", span)? };
6827    let k = if x.rank() == 0 { 1 } else { x.shape[0] };
6828    let frames = if k == 0 { 0 } else { radices.len() / k };
6829    let n = values.len();
6830    let mut out = vec![0.0f64; k * frames * n];
6831    let mut radix = vec![0.0f64; k];
6832    let mut cell = vec![0.0f64; k];
6833    for p in 0..frames {
6834        for (i, r) in radix.iter_mut().enumerate() {
6835            *r = radices[i * frames + p];
6836        }
6837        for (j, &v) in values.iter().enumerate() {
6838            encode_one(&radix, v, &mut cell, tol);
6839            for i in 0..k {
6840                out[(i * frames + p) * n + j] = cell[i];
6841            }
6842        }
6843    }
6844    let mut shape = x.shape.clone();
6845    shape.extend_from_slice(&y.shape);
6846    Ok(Array::new(shape, narrow(out, empty || (is_integral(x) && is_integral(y)))))
6847}
6848
6849/// The number of binary digits `#: y` uses: enough for the largest magnitude
6850/// in the whole argument, and never fewer than one.
6851fn bit_width(values: &[f64], span: Span) -> Result<usize> {
6852    // Nothing to encode needs no digits at all: `$ #: i. 0` is `0 0`.
6853    if values.is_empty() {
6854        return Ok(0);
6855    }
6856    let mut m = 0.0f64;
6857    for &v in values {
6858        if !v.is_finite() {
6859            return Err(Error::domain("cannot encode an infinite value", span));
6860        }
6861        m = m.max(v.abs());
6862    }
6863    let whole = m.floor();
6864    if whole >= 1e15 {
6865        return Err(Error::domain("the value is too large to encode in binary", span));
6866    }
6867    let mut w = 1usize;
6868    let mut n = whole as i64;
6869    while n > 1 {
6870        n /= 2;
6871        w += 1;
6872    }
6873    Ok(w)
6874}
6875
6876/// One value written in the radices `radix`, most significant first. A radix
6877/// of 0 takes whatever is left, which is how both languages spell "and the
6878/// rest".
6879///
6880/// Each digit is a residue, and it is taken with the dialect's tolerance as
6881/// `|` itself is: `2 2 #: 4 - 1e_14` is `0 0` in jconsole, not the `1 2` an
6882/// exact quotient leaves.
6883fn encode_one(radix: &[f64], v: f64, out: &mut [f64], tol: Tol) {
6884    let mut rem = v;
6885    for i in (0..radix.len()).rev() {
6886        let b = radix[i];
6887        if b == 0.0 {
6888            out[i] = rem;
6889            rem = 0.0;
6890        } else {
6891            let r = tol.residue(b, rem);
6892            out[i] = r;
6893            rem = (rem - r) / b;
6894        }
6895    }
6896}
6897
6898/// `x #: y` / `x ⊤ y`: the digits become the LEADING axis, so the result has
6899/// shape `(#x), $y`. J applies this per atom of y (right rank 0) and APL to
6900/// the whole of it (right rank infinite); the operation itself is the same.
6901fn encode(x: &Array, y: &Array, tol: Tol, span: Span) -> Result<Array> {
6902    let radix = digits_of(x, "encode", span)?;
6903    let values = digits_of(y, "encode", span)?;
6904    let k = radix.len();
6905    let n = values.len();
6906    let mut out = vec![0.0f64; k * n];
6907    let mut cell = vec![0.0f64; k];
6908    for (j, &v) in values.iter().enumerate() {
6909        encode_one(&radix, v, &mut cell, tol);
6910        // Each digit is a residue, so a digit with no value is refused
6911        // where the residue itself would be: `5 #: _` has none.
6912        if cell.iter().any(|&d| tol.made_nan(d, v, 0.0)) {
6913            return Err(Error::nan(
6914                format!("`{}` has no digits in this base", j_number(v)),
6915                span,
6916            ));
6917        }
6918        for i in 0..k {
6919            out[i * n + j] = cell[i];
6920        }
6921    }
6922    // The digit axis is x's own shape: a scalar radix adds no axis at all,
6923    // which is why `2 #: 5` is a scalar and `2 2 #: 5` is a two-element list.
6924    let mut shape = if x.rank() == 0 { Vec::new() } else { vec![k] };
6925    shape.extend_from_slice(&y.shape);
6926    Ok(Array::new(shape, narrow(out, is_integral(x) && is_integral(y))))
6927}
6928
6929/// `#: y`: base-2 encode of the whole argument, the digits trailing.
6930fn encode_bits(y: &Array, tol: Tol, span: Span) -> Result<Array> {
6931    let values = digits_of(y, "encode", span)?;
6932    let k = bit_width(&values, span)?;
6933    let radix = vec![2.0; k];
6934    let mut out = vec![0.0f64; values.len() * k];
6935    for (j, &v) in values.iter().enumerate() {
6936        encode_one(&radix, v, &mut out[j * k..(j + 1) * k], tol);
6937    }
6938    let mut shape = y.shape.clone();
6939    shape.push(k);
6940    Ok(Array::new(shape, narrow(out, is_integral(y))))
6941}
6942
6943/// `x ,: y`: the two arguments as the items of a new leading axis. A scalar
6944/// spreads over the other argument's shape, and two scalars become
6945/// one-element lists (`1 ,: 2` has shape 2 1); otherwise the framing
6946/// machinery's own fill brings the two cells to a common shape.
6947fn laminate(x: &Array, y: &Array, span: Span) -> Result<Array> {
6948    let spread = |a: &Array, other: &Array| -> Array {
6949        if a.rank() != 0 {
6950            return a.clone();
6951        }
6952        let shape = if other.rank() == 0 { vec![1] } else { other.shape.clone() };
6953        let n: usize = shape.iter().product();
6954        let mut data = Data::empty(a.dtype());
6955        for _ in 0..n {
6956            push_elem(&mut data, &a.data, 0);
6957        }
6958        Array::new(shape, data)
6959    };
6960    assemble(&[2], vec![spread(x, y), spread(y, x)], span)
6961}
6962
6963/// `⍪ y`: one row per item, holding that item's elements.
6964fn table_of(y: &Array) -> Array {
6965    let shape = match y.rank() {
6966        0 => vec![1, 1],
6967        _ => vec![y.items(), y.item_size()],
6968    };
6969    Array::new(shape, y.data.clone())
6970}
6971
6972/// One application of the APL kind: between two ITEMS.
6973///
6974/// APL hands a function the contents of an item, not the item, and puts a
6975/// result that is not a simple scalar back under an enclosure so it can take
6976/// one place in the array being built. J leaves its boxes shut instead,
6977/// which is where the two languages part on `∘.⌽` and on `,/`.
6978fn item_dyad(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
6979    let r = u.dyad(&open_cell(x), &open_cell(y), ctx, span)?;
6980    Ok(enclose(&r, Enclose::ExceptSimpleScalar))
6981}
6982
6983/// `x ∘.u y` (APL): u between every element of x and every element of y.
6984///
6985/// The elements are atoms whatever u's rank — `1 2∘.,3 4` is a 2-by-2 table
6986/// of pairs, not one catenation — and each is disclosed on the way in, so
6987/// `¯1 0 1∘.⌽⊂m` rotates the matrix rather than the enclosure holding it.
6988/// The result of each application is enclosed again unless it is already a
6989/// simple scalar.
6990fn outer_product(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
6991    let mut frame = x.shape.clone();
6992    frame.extend_from_slice(&y.shape);
6993    let (nx, ny) = (x.count(), y.count());
6994    let n = nx * ny;
6995    if n == 0 {
6996        return assemble(&frame, Vec::new(), span);
6997    }
6998    let (xr, yr) = (x.to_row_major(), y.to_row_major());
6999    let cells = each_cell(n, nx.max(ny).max(n), u.is_pure(), ctx, |i, c| {
7000        item_dyad(u, &atom(&xr, i / ny), &atom(&yr, i % ny), c, span)
7001    })?;
7002    assemble_items(&frame, cells, span)
7003}
7004
7005/// `x u/ y`: u applied to every pair of cells, x's frame before y's.
7006///
7007/// The cells are the ones u's own ranks ask for, which is why `1 2 3 +/ 10 20`
7008/// is a 3-by-2 table (atoms both sides) while `x ,/ y` is a single catenation
7009/// (`,` takes its arguments whole). APL spells the same table `∘.u` and reads
7010/// it by items instead, so that is where its dyad goes.
7011fn table(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
7012    if ctx.cfg.rules.lang == crate::Lang::Apl {
7013        return outer_product(u, x, y, ctx, span);
7014    }
7015    let ranks = u.ranks();
7016    let fxl = x.rank() - effective_rank(ranks[1], x.rank());
7017    let fyl = y.rank() - effective_rank(ranks[2], y.rank());
7018    let mut frame = x.shape[..fxl].to_vec();
7019    frame.extend_from_slice(&y.shape[..fyl]);
7020    let nx: usize = x.shape[..fxl].iter().product();
7021    let ny: usize = y.shape[..fyl].iter().product();
7022    let n = nx * ny;
7023    if n == 0 {
7024        return assemble(&frame, Vec::new(), span);
7025    }
7026    if frame.is_empty() {
7027        return u.dyad(x, y, ctx, span);
7028    }
7029    let work = x.count().max(y.count()).max(n);
7030    let cells = each_cell(n, work, u.is_pure(), ctx, |i, c| {
7031        u.dyad(&x.cell_at(fxl, i / ny), &y.cell_at(fyl, i % ny), c, span)
7032    })?;
7033    assemble(&frame, cells, span)
7034}
7035
7036/// The same verb with a different index origin — APL's `f⍠('IO' n)`.
7037///
7038/// The origin is a dialect setting, resolved into the primitives when the
7039/// program is compiled, so overriding it for one application means deriving
7040/// the verb again with the other value. None where the verb has no origin
7041/// to change, which is what makes `⎕IO` not one of its options.
7042pub(crate) fn with_origin(v: &Verb, origin: i64) -> Option<Verb> {
7043    match v {
7044        Verb::Prim(p) => {
7045            let mut out = *p;
7046            let mut changed = false;
7047            out.monad = match p.monad {
7048                MonadOp::GradeUp { .. } => {
7049                    changed = true;
7050                    MonadOp::GradeUp { origin }
7051                }
7052                MonadOp::GradeDown { .. } => {
7053                    changed = true;
7054                    MonadOp::GradeDown { origin }
7055                }
7056                MonadOp::IotaApl { .. } => {
7057                    changed = true;
7058                    MonadOp::IotaApl { origin }
7059                }
7060                MonadOp::Indices { boxed_coords, .. } => {
7061                    changed = true;
7062                    MonadOp::Indices { origin, boxed_coords }
7063                }
7064                MonadOp::Roll { fixed, float_at_zero, .. } => {
7065                    changed = true;
7066                    MonadOp::Roll { origin, fixed, float_at_zero }
7067                }
7068                other => other,
7069            };
7070            out.dyad = match p.dyad {
7071                DyadOp::IndexOf { vector_left, .. } => {
7072                    changed = true;
7073                    DyadOp::IndexOf { origin, vector_left }
7074                }
7075                DyadOp::IndexOfLast { .. } => {
7076                    changed = true;
7077                    DyadOp::IndexOfLast { origin }
7078                }
7079                DyadOp::CollateGrade { down, .. } => {
7080                    changed = true;
7081                    DyadOp::CollateGrade { down, origin }
7082                }
7083                DyadOp::Squad { leading, .. } => {
7084                    changed = true;
7085                    DyadOp::Squad { origin, leading }
7086                }
7087                DyadOp::Pick { .. } => {
7088                    changed = true;
7089                    DyadOp::Pick { origin }
7090                }
7091                DyadOp::SelectAxis { axis, rank, .. } => {
7092                    changed = true;
7093                    DyadOp::SelectAxis { axis, rank, origin }
7094                }
7095                DyadOp::Deal { fixed, .. } => {
7096                    changed = true;
7097                    DyadOp::Deal { origin, fixed }
7098                }
7099                other => other,
7100            };
7101            changed.then_some(Verb::Prim(out))
7102        }
7103        Verb::Rank(u, r) => Some(Verb::Rank(Box::new(with_origin(u, origin)?), *r)),
7104        Verb::Reduce(u) => Some(Verb::Reduce(Box::new(with_origin(u, origin)?))),
7105        Verb::NWise(u) => Some(Verb::NWise(Box::new(with_origin(u, origin)?))),
7106        Verb::Windowed(u, k) => Some(Verb::Windowed(Box::new(with_origin(u, origin)?), *k)),
7107        Verb::Commute(u) => Some(Verb::Commute(Box::new(with_origin(u, origin)?))),
7108        Verb::Each(u, e) => Some(Verb::Each(Box::new(with_origin(u, origin)?), *e)),
7109        Verb::Fit(u, n) => Some(Verb::Fit(Box::new(with_origin(u, origin)?), *n)),
7110        Verb::AlongAxis(u, k) => Some(Verb::AlongAxis(Box::new(with_origin(u, origin)?), *k)),
7111        _ => None,
7112    }
7113}
7114
7115// ------------------------------------------------------- inner product
7116
7117/// The scalar operation a bare primitive performs dyadically, for the fast
7118/// paths that recognise `+` and `*` rather than applying them.
7119fn scalar_dyad_of(v: &Verb) -> Option<ScalarDyad> {
7120    match v {
7121        Verb::Prim(p) => match p.dyad {
7122            DyadOp::Scalar(op) => Some(op),
7123            _ => None,
7124        },
7125        _ => None,
7126    }
7127}
7128
7129/// True where the verb folds a list with one scalar operation, which is
7130/// what `+/` and `∧/` are and what the matrix product's fast path needs.
7131fn folds_with(u: &Verb, op: ScalarDyad) -> bool {
7132    matches!(u, Verb::Reduce(inner) if scalar_dyad_of(inner) == Some(op))
7133}
7134
7135/// `x u . v y`: the inner product.
7136///
7137/// x is taken in cells at v's dyadic left rank, or at rank 1 where that is
7138/// smaller — the rule that makes `+/ . *` a matrix product and leaves a
7139/// whole-argument v (`,`, `,:`) reading the whole of x. Each cell meets the
7140/// WHOLE of y under v, and u folds what comes back.
7141fn inner_product(
7142    u: &Verb,
7143    v: &Verb,
7144    apl: bool,
7145    x: &Array,
7146    y: &Array,
7147    ctx: &mut Ctx<'_>,
7148    span: Span,
7149) -> Result<Array> {
7150    if let Some(a) = matrix_product(u, v, x, y, span) {
7151        return Ok(a);
7152    }
7153    // APL pairs each row of x with each COLUMN of y, which parts from J's
7154    // reading exactly where v does not apply to atoms.
7155    if apl && scalar_dyad_of(v).is_none() {
7156        return apl_inner_product(u, v, x, y, ctx, span);
7157    }
7158    if !apl {
7159        return inner_cells(u, v, false, x, y, ctx, span);
7160    }
7161    // A scalar v pairs one element of the row with one element of the
7162    // column, which is the leading-axis pairing J spells out and APL's own
7163    // conformability rule — about whole applications — does not describe.
7164    // The definition asks for that pairing, so the inner application runs
7165    // under it and the caller's rule is put back afterwards.
7166    let saved = ctx.cfg.agreement;
7167    ctx.cfg.agreement = Agreement::LeadingPrefix;
7168    let out = inner_cells(u, v, true, x, y, ctx, span);
7169    ctx.cfg.agreement = saved;
7170    out
7171}
7172
7173/// Every element enclosed once more, which is what an each does to the
7174/// values it brings back. A simple array is all simple scalars and cannot
7175/// be nested any further, so it is returned as it stands.
7176fn enclose_elements(a: &Array) -> Array {
7177    if a.dtype() == DType::Box { boxed_elements(a) } else { a.clone() }
7178}
7179
7180/// The fold that closes the cells of an inner product.
7181///
7182/// APL's definition is `f/¨ (⊂[last]x) ∘.g (⊂[first]y)`: the each is part of
7183/// it, so what the fold makes of one pair is enclosed unless it is already a
7184/// simple scalar. `1 2+.×3 4` is a number either way; `1 2,.+3 4` is an
7185/// enclosed vector, and only APL says so. Under `InnerEach::OnPair` the each
7186/// sits on the pairing instead and the fold's own value stands as the cell.
7187fn inner_fold(
7188    u: &Verb,
7189    apl: bool,
7190    inner: &Array,
7191    ctx: &mut Ctx<'_>,
7192    span: Span,
7193) -> Result<Array> {
7194    let folded = u.monad(inner, ctx, span)?;
7195    Ok(if apl && each_on_fold(ctx) { enclose_elements(&folded) } else { folded })
7196}
7197
7198/// Whether the dialect puts the inner product's each on the fold.
7199fn each_on_fold(ctx: &Ctx<'_>) -> bool {
7200    ctx.cfg.rules.inner_each == InnerEach::OnFold
7201}
7202
7203/// The inner product by the cell machinery: x's cells at v's dyadic left
7204/// rank, or at rank 1 where that is smaller, each against the whole of y.
7205fn inner_cells(
7206    u: &Verb,
7207    v: &Verb,
7208    apl: bool,
7209    x: &Array,
7210    y: &Array,
7211    ctx: &mut Ctx<'_>,
7212    span: Span,
7213) -> Result<Array> {
7214    let cell_rank = effective_rank(v.ranks()[1].max(1), x.rank());
7215    let frame_rank = x.rank() - cell_rank;
7216    if frame_rank == 0 {
7217        let inner = v.dyad(x, y, ctx, span)?;
7218        return inner_fold(u, apl, &inner, ctx, span);
7219    }
7220    let frame = x.shape[..frame_rank].to_vec();
7221    let n: usize = frame.iter().product();
7222    if n == 0 {
7223        return assemble(&frame, Vec::new(), span);
7224    }
7225    let work = x.count().max(y.count());
7226    let pure = u.is_pure() && v.is_pure();
7227    let items = apl && !each_on_fold(ctx);
7228    let cells = each_cell(n, work, pure, ctx, |i, c| {
7229        let inner = v.dyad(&x.cell_at(frame_rank, i), y, c, span)?;
7230        inner_fold(u, apl, &inner, c, span)
7231    })?;
7232    if items { assemble_items(&frame, cells, span) } else { assemble(&frame, cells, span) }
7233}
7234
7235/// APL's `f.g` where g is not a scalar function: every vector along x's
7236/// LAST axis meets every vector along y's FIRST axis, and f folds each
7237/// result. With a scalar g this is the same as J's reading, which is the
7238/// path that runs it.
7239///
7240/// The two vectors meet whole under `InnerEach::OnFold` and element by
7241/// element under `InnerEach::OnPair`: `(2 2⍴⍳4),.,2 2⍴⍳4` opens with
7242/// `1 2 1 3` under the first and `1 1 2 3` under the second.
7243fn apl_inner_product(
7244    u: &Verb,
7245    v: &Verb,
7246    x: &Array,
7247    y: &Array,
7248    ctx: &mut Ctx<'_>,
7249    span: Span,
7250) -> Result<Array> {
7251    // A scalar argument stands for as many copies of itself as the other
7252    // side's shared axis asks for; two scalars share an axis of one.
7253    let k = match (x.rank(), y.rank()) {
7254        (0, 0) => 1,
7255        (0, _) => y.shape[0],
7256        _ => x.shape[x.rank() - 1],
7257    };
7258    if x.rank() > 0 && y.rank() > 0 && x.shape[x.rank() - 1] != y.shape[0] {
7259        return Err(Error::new(
7260            ErrorKind::Length,
7261            format!("inner product over {} and {} elements", x.shape[x.rank() - 1], y.shape[0]),
7262            Some(span),
7263        ));
7264    }
7265    let lead: &[usize] = if x.rank() > 0 { &x.shape[..x.rank() - 1] } else { &[] };
7266    let trail: &[usize] = if y.rank() > 0 { &y.shape[1..] } else { &[] };
7267    let rows: usize = lead.iter().product();
7268    let cols: usize = trail.iter().product();
7269    let mut frame = lead.to_vec();
7270    frame.extend_from_slice(trail);
7271    let n = rows * cols;
7272    if n == 0 {
7273        return assemble(&frame, Vec::new(), span);
7274    }
7275    let vector = |d: &Data, at: &dyn Fn(usize) -> usize| {
7276        let mut out = Data::empty(d.dtype());
7277        for t in 0..k {
7278            out.push_from(d, at(t));
7279        }
7280        Array::new(vec![k], out)
7281    };
7282    let pure = u.is_pure() && v.is_pure();
7283    let on_fold = each_on_fold(ctx);
7284    let paired = Verb::Each(Box::new(v.clone()), Enclose::ExceptSimpleScalar);
7285    let cells = each_cell(n, x.count().max(y.count()), pure, ctx, |i, c| {
7286        let (r, col) = (i / cols, i % cols);
7287        let left = vector(&x.data, &|t| if x.rank() > 0 { r * k + t } else { 0 });
7288        let right = vector(&y.data, &|t| if y.rank() > 0 { t * cols + col } else { 0 });
7289        let inner = if on_fold {
7290            v.dyad(&left, &right, c, span)?
7291        } else {
7292            paired.dyad(&left, &right, c, span)?
7293        };
7294        inner_fold(u, true, &inner, c, span)
7295    })?;
7296    if on_fold { assemble(&frame, cells, span) } else { assemble_items(&frame, cells, span) }
7297}
7298
7299/// `+/ . *` (APL `+.×`) over real machine numbers: the matrix product, run
7300/// as a blocked pass over the two buffers instead of by the cell machinery.
7301/// The shape rule is the general one — x's last axis pairs with y's first —
7302/// so an argument of any rank comes through here. None sends the
7303/// application back to the general path.
7304fn matrix_product(u: &Verb, v: &Verb, x: &Array, y: &Array, span: Span) -> Option<Array> {
7305    if !folds_with(u, ScalarDyad::Add) || scalar_dyad_of(v) != Some(ScalarDyad::Mul) {
7306        return None;
7307    }
7308    if x.rank() == 0 || y.rank() == 0 {
7309        return None;
7310    }
7311    let k = x.shape[x.rank() - 1];
7312    if k != y.shape[0] {
7313        return None;
7314    }
7315    let rows: usize = x.shape[..x.rank() - 1].iter().product();
7316    let cols: usize = y.shape[1..].iter().product();
7317    let mut shape = x.shape[..x.rank() - 1].to_vec();
7318    shape.extend_from_slice(&y.shape[1..]);
7319    if crate::limits::elements(&shape, span).is_err() {
7320        return None;
7321    }
7322    let whole = matches!(x.dtype(), DType::Bool | DType::I64)
7323        && matches!(y.dtype(), DType::Bool | DType::I64);
7324    if whole
7325        && let (Some(xs), Some(ys)) = (x.to_i64_vec(), y.to_i64_vec())
7326        && let Some(out) = matmul_whole(&xs, &ys, rows, k, cols)
7327    {
7328        return Some(Array::new(shape, Data::I64(out.into())));
7329    }
7330    let (xs, ys) = (x.to_f64_vec()?, y.to_f64_vec()?);
7331    let out = par::fill_rows(rows, cols, rows * k * cols, |r0, part| {
7332        matmul_f64(&xs, &ys, k, cols, r0, part);
7333    });
7334    Some(Array::new(shape, Data::F64(out.into())))
7335}
7336
7337/// Elements a block of the matrix product's inner axis covers at once: the
7338/// slice of y one pass over the output rows reuses. 128 rows of a 1000-wide
7339/// table is a megabyte, which is what a second-level cache holds.
7340const MATMUL_BLOCK: usize = 128;
7341
7342#[inline(always)]
7343fn matmul_f64_body(xs: &[f64], ys: &[f64], k: usize, n: usize, r0: usize, out: &mut [f64]) {
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 += a * b;
7357                }
7358            }
7359        }
7360    }
7361}
7362
7363multiversioned! {
7364    /// One block of output rows of a float matrix product. `out` is the
7365    /// block, `r0` the row it starts at; the accumulator is the output
7366    /// itself, which arrives zeroed.
7367    fn matmul_f64(
7368        xs: &[f64],
7369        ys: &[f64],
7370        k: usize,
7371        n: usize,
7372        r0: usize,
7373        out: &mut [f64],
7374    ) -> () = matmul_f64_body;
7375}
7376
7377#[inline(always)]
7378fn matmul_i64_body(xs: &[i64], ys: &[i64], k: usize, n: usize, r0: usize, out: &mut [i64]) {
7379    if n == 0 {
7380        return;
7381    }
7382    let rows = out.len() / n;
7383    for k0 in (0..k).step_by(MATMUL_BLOCK) {
7384        let k1 = (k0 + MATMUL_BLOCK).min(k);
7385        for r in 0..rows {
7386            let left = &xs[(r0 + r) * k..(r0 + r + 1) * k];
7387            let dst = &mut out[r * n..(r + 1) * n];
7388            for (t, &a) in left.iter().enumerate().take(k1).skip(k0) {
7389                let row = &ys[t * n..(t + 1) * n];
7390                for (o, &b) in dst.iter_mut().zip(row) {
7391                    *o = o.wrapping_add(a.wrapping_mul(b));
7392                }
7393            }
7394        }
7395    }
7396}
7397
7398multiversioned! {
7399    /// One block of output rows of an integer matrix product. Reached only
7400    /// where the values cannot overflow, so wrapping arithmetic is exact
7401    /// arithmetic here and the loop vectorises.
7402    fn matmul_i64(
7403        xs: &[i64],
7404        ys: &[i64],
7405        k: usize,
7406        n: usize,
7407        r0: usize,
7408        out: &mut [i64],
7409    ) -> () = matmul_i64_body;
7410}
7411
7412/// The same product over integers. None where a product or a sum leaves
7413/// i64, which sends the whole pass to floats, as every other integer
7414/// primitive does.
7415fn matmul_whole(xs: &[i64], ys: &[i64], rows: usize, k: usize, n: usize) -> Option<Vec<i64>> {
7416    // A bound on the largest partial sum decides once, for the whole pass,
7417    // whether the plain loop can overflow at all. Where it cannot, the
7418    // vectorised kernel runs; where it might, the checked loop does, and
7419    // leaving i64 anywhere sends the whole product to floats.
7420    let bound = |v: &[i64]| v.iter().map(|&a| (a as i128).abs()).max().unwrap_or(0);
7421    if bound(xs).saturating_mul(bound(ys)).saturating_mul(k as i128) <= i64::MAX as i128 {
7422        return Some(par::fill_rows(rows, n, rows * k * n, |r0, part| {
7423            matmul_i64(xs, ys, k, n, r0, part);
7424        }));
7425    }
7426    let mut out = vec![0i64; rows * n];
7427    for r in 0..rows {
7428        let left = &xs[r * k..(r + 1) * k];
7429        let dst = &mut out[r * n..(r + 1) * n];
7430        for (t, &a) in left.iter().enumerate() {
7431            for (o, &b) in dst.iter_mut().zip(&ys[t * n..(t + 1) * n]) {
7432                *o = a.checked_mul(b).and_then(|p| o.checked_add(p))?;
7433            }
7434        }
7435    }
7436    Some(out)
7437}
7438
7439/// Rows a determinant by minors is computed for at most. The recursion is
7440/// memoised on the set of rows still in play, so the cost is `2^n` cells
7441/// rather than `n!` — but it is still exponential, and past this the
7442/// message names the limit instead of running out of memory.
7443const DETERMINANT_MINORS_MAX: usize = 16;
7444
7445/// `u . v y`: the determinant by minors down the FIRST column — for each
7446/// row in turn, that row's leading element under v with the determinant of
7447/// the table the row and the column leave behind, all folded by u. With no
7448/// columns left the value is v's identity element; with no rows left it is
7449/// u over nothing.
7450fn determinant(
7451    u: &Verb,
7452    v: &Verb,
7453    apl: bool,
7454    y: &Array,
7455    ctx: &mut Ctx<'_>,
7456    span: Span,
7457) -> Result<Array> {
7458    if apl {
7459        return Err(Error::domain("an inner product has no monadic meaning in APL", span));
7460    }
7461    // The determinant is of a table, so an argument of higher rank frames
7462    // one answer per 2-cell. Nothing above applies the rank machinery for
7463    // this verb: its dyad reads both arguments whole.
7464    if y.rank() > 2 {
7465        let frame = y.shape[..y.rank() - 2].to_vec();
7466        let n: usize = frame.iter().product();
7467        let pure = u.is_pure() && v.is_pure();
7468        let cells = each_cell(n, y.count(), pure, ctx, |i, c| {
7469            determinant(u, v, apl, &y.cell_at(y.rank() - 2, i), c, span)
7470        })?;
7471        return assemble(&frame, cells, span);
7472    }
7473    let rows = y.items();
7474    let cols = y.item_size();
7475    if folds_with(u, ScalarDyad::Sub)
7476        && scalar_dyad_of(v) == Some(ScalarDyad::Mul)
7477        && rows == cols
7478        && rows >= 3
7479        && matches!(y.dtype(), DType::Bool | DType::I64 | DType::F64)
7480        && let Some(values) = y.to_f64_vec()
7481    {
7482        return Ok(Array::scalar_f64(determinant_lu(values, rows)));
7483    }
7484    if rows > DETERMINANT_MINORS_MAX {
7485        return Err(Error::not_yet(
7486            format!(
7487                "a determinant of more than {DETERMINANT_MINORS_MAX} rows by minors \
7488                 (only -/ . * over machine numbers has a direct method)"
7489            ),
7490            span,
7491        ));
7492    }
7493    let mut seen: HashMap<u64, Array> = HashMap::new();
7494    let all = if rows == 64 { u64::MAX } else { (1u64 << rows) - 1 };
7495    minors(u, v, y, cols, rows, all, &mut seen, ctx, span)
7496}
7497
7498/// One node of the expansion: the determinant of the table `left` still
7499/// names rows of, with the leading columns the recursion has consumed
7500/// already dropped.
7501#[allow(clippy::too_many_arguments)]
7502fn minors(
7503    u: &Verb,
7504    v: &Verb,
7505    y: &Array,
7506    cols: usize,
7507    rows: usize,
7508    left: u64,
7509    seen: &mut HashMap<u64, Array>,
7510    ctx: &mut Ctx<'_>,
7511    span: Span,
7512) -> Result<Array> {
7513    if let Some(a) = seen.get(&left) {
7514        return Ok(a.clone());
7515    }
7516    // One row and one column go at every step, so how many rows are left
7517    // says which column this node starts at.
7518    let column = rows - left.count_ones() as usize;
7519    let value = if column >= cols {
7520        let data = reduce_identity(v, 1, ctx.cfg.rules.lang).ok_or_else(|| {
7521            Error::not_yet(
7522                format!("the identity element of {} (a determinant with no columns)", v.name()),
7523                span,
7524            )
7525        })?;
7526        Array::new(Vec::new(), data)
7527    } else if left == 0 {
7528        u.monad(&Array::new(vec![0], Data::empty(DType::I64)), ctx, span)?
7529    } else {
7530        let mut terms = Vec::with_capacity(left.count_ones() as usize);
7531        for r in 0..rows {
7532            if left & (1 << r) == 0 {
7533                continue;
7534            }
7535            let minor = minors(u, v, y, cols, rows, left & !(1 << r), seen, ctx, span)?;
7536            let head = Array::new(Vec::new(), y.data.slice(r * cols + column, r * cols + column + 1));
7537            terms.push(v.dyad(&head, &minor, ctx, span)?);
7538        }
7539        let n = terms.len();
7540        u.monad(&assemble(&[n], terms, span)?, ctx, span)?
7541    };
7542    seen.insert(left, value.clone());
7543    Ok(value)
7544}
7545
7546/// `-/ . * y` over machine numbers: the determinant by Gaussian
7547/// elimination with partial pivoting, which is how the reference computes
7548/// it from three rows up — and why its answer there is a float even where
7549/// every element is whole.
7550fn determinant_lu(mut a: Vec<f64>, n: usize) -> f64 {
7551    let mut det = 1.0f64;
7552    for c in 0..n {
7553        let mut pivot = c;
7554        for r in c + 1..n {
7555            if a[r * n + c].abs() > a[pivot * n + c].abs() {
7556                pivot = r;
7557            }
7558        }
7559        if a[pivot * n + c] == 0.0 {
7560            return 0.0;
7561        }
7562        if pivot != c {
7563            for j in 0..n {
7564                a.swap(c * n + j, pivot * n + j);
7565            }
7566            det = -det;
7567        }
7568        let head = a[c * n + c];
7569        det *= head;
7570        for r in c + 1..n {
7571            let factor = a[r * n + c] / head;
7572            if factor == 0.0 {
7573                continue;
7574            }
7575            for j in c..n {
7576                a[r * n + j] -= factor * a[c * n + j];
7577            }
7578        }
7579    }
7580    det
7581}
7582
7583/// Monadic meaning of a primitive, applied to one cell.
7584fn monad_op(p: &Prim, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
7585    let apl = ctx.cfg.rules.lang == crate::Lang::Apl;
7586    let out = monad_op_inner(p, y, ctx, span);
7587    if apl { out.map(tightened_mixed) } else { out }
7588}
7589
7590/// Every APL result passes through [`tightened_mixed`] on the way out, so
7591/// the mixed simple form never outlives the mixture that called for it.
7592fn monad_op_inner(p: &Prim, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
7593    match p.monad {
7594        MonadOp::Scalar(op) => scalar_monad(op, y, ctx.cfg, span),
7595        MonadOp::ShapeOf => {
7596            Ok(carry_exact(Array::from_i64(y.shape.iter().map(|&n| n as i64).collect()), y))
7597        }
7598        MonadOp::Tally => Ok(carry_exact(Array::scalar_i64(y.items() as i64), y)),
7599        MonadOp::Ravel => Ok(Array::new(vec![y.count()], y.data.clone())),
7600        // `,. y` is one row per item, the item raveled along it. An atom is
7601        // one item whose ravel is one element, so it becomes a 1-by-1
7602        // table: `$ ,. 5` is `1 1`, which is where `,"_1` alone would stop
7603        // one axis short.
7604        MonadOp::RavelItems => {
7605            let (items, width) = if y.rank() == 0 {
7606                (1usize, 1usize)
7607            } else {
7608                (y.shape[0], y.shape[1..].iter().product::<usize>())
7609            };
7610            Ok(Array::new(vec![items, width], y.to_row_major().data))
7611        }
7612        MonadOp::TransposeAxes => Ok(transpose_axes(y)),
7613        MonadOp::Head => Ok(head(y)),
7614        MonadOp::Behead => behead(y, span),
7615        MonadOp::Tail => Ok(tail(y)),
7616        MonadOp::Curtail => Ok(curtail(y)),
7617        MonadOp::Reverse => Ok(reverse(y)),
7618        // Monadic `∪` stays nub over ITEMS at any rank, which is a
7619        // recorded divergence from GNU APL's vectors-only monad.
7620        MonadOp::Nub => Ok(nub(y, ctx.cfg.tol)),
7621        MonadOp::GradeUp { origin } | MonadOp::GradeDown { origin } => {
7622            check_gradable(y, ctx.cfg.rules, span)?;
7623            // APL grades the ITEMS of an array, so a scalar has none to
7624            // grade; J answers with the one-item permutation.
7625            if ctx.cfg.rules.lang == crate::Lang::Apl && y.rank() == 0 {
7626                return Err(Error::domain("a grade needs an array, not a scalar", span));
7627            }
7628            let down = matches!(p.monad, MonadOp::GradeDown { .. });
7629            let order = grade_order(y, down, Grading::of(ctx.cfg.rules, ctx.cfg.tol));
7630            Ok(Array::from_i64(order.iter().map(|&i| origin + i as i64).collect()))
7631        }
7632        MonadOp::IotaJ => iota_j(y, ctx.cfg.near(), span),
7633        MonadOp::IotaApl { origin } => iota_apl(y, origin, ctx.cfg.near(), span),
7634        MonadOp::Echo => {
7635            (ctx.out)(&format!("{}\n", crate::fmt::format_array(y, &ctx.cfg.fmt)));
7636            Ok(Array::empty(DType::I64))
7637        }
7638        MonadOp::ReadStream => {
7639            stream_number(y, 1, "1!:1 reads", span)?;
7640            let line = ctx.read_line(span)?;
7641            Ok(Array::from_chars(line.chars().collect()))
7642        }
7643        MonadOp::TypeCode => Ok(Array::scalar_i64(type_code(y))),
7644        MonadOp::Sparse => crate::sparse::sparsify(y, span),
7645        MonadOp::Dense => Ok(y.densified()),
7646        MonadOp::PrimeCount => {
7647            let n = y
7648                .to_i64_vec_near(ctx.cfg.near())
7649                .ok_or_else(|| Error::domain("the prime count needs an integer", span))?;
7650            let v = n.first().copied().unwrap_or(0);
7651            Ok(carry_exact(Array::scalar_i64(primes_below(v, span)?), y))
7652        }
7653        MonadOp::IndicesInverse => indices_inverse(y, ctx.cfg.near(), span),
7654        MonadOp::Same => Ok(y.clone()),
7655        MonadOp::Format => Ok(format_chars(y, &ctx.cfg.fmt)),
7656        MonadOp::DecodeBits => decode(None, y, ctx.cfg.tol, span).map(|r| carry_exact(r, y)),
7657        MonadOp::EncodeBits => encode_bits(y, ctx.cfg.tol, span).map(|r| carry_exact(r, y)),
7658        MonadOp::Itemize => {
7659            let mut shape = vec![1usize];
7660            shape.extend_from_slice(&y.shape);
7661            Ok(Array::new(shape, y.data.clone()))
7662        }
7663        MonadOp::TableOf => Ok(table_of(y)),
7664        MonadOp::Enclose(rule) => Ok(enclose(y, rule)),
7665        MonadOp::Open => Ok(open_cell(y)),
7666        MonadOp::Raze => raze(y, span),
7667        MonadOp::Catalogue => catalogue(y, span),
7668        MonadOp::AtomicRep => atomic_rep(y, ctx, span),
7669        MonadOp::RazeIn => raze_in(y, ctx.cfg.tol, span),
7670        MonadOp::First => Ok(first(y)),
7671        MonadOp::Enlist => enlist(y, span),
7672        MonadOp::Depth { signed } => {
7673            let d = depth(y);
7674            Ok(Array::scalar_i64(if signed && d > 1 && !uniform(y) { -d } else { d }))
7675        }
7676        MonadOp::Indices { origin, boxed_coords } => {
7677            where_indices(y, origin, boxed_coords, ctx.cfg.near(), span)
7678        }
7679        MonadOp::Steps => steps(y, span),
7680        MonadOp::ToExact => to_exact(y, span),
7681        MonadOp::NthPrime => {
7682            let n = y
7683                .to_i64_vec_near(ctx.cfg.near())
7684                .ok_or_else(|| Error::domain("the prime index must be an integer", span))?;
7685            let v = n.first().copied().unwrap_or(0);
7686            Ok(carry_exact(Array::scalar_i64(nth_prime(v, span)?), y))
7687        }
7688        MonadOp::PrimeFactors => {
7689            let n = y
7690                .to_i64_vec_near(ctx.cfg.near())
7691                .ok_or_else(|| Error::domain("prime factors need an integer", span))?;
7692            let v = n.first().copied().unwrap_or(0);
7693            Ok(carry_exact(Array::from_i64(prime_factors(v, span)?), y))
7694        }
7695        MonadOp::MatrixInverse => matrix_inverse(y, span),
7696        MonadOp::Roll { origin, fixed, float_at_zero } => {
7697            roll(y, origin, fixed, float_at_zero, ctx.cfg.near(), span)
7698        }
7699        MonadOp::ComplexParts { polar } => complex_parts(y, polar, span),
7700        MonadOp::SelfClassify => Ok(self_classify(y, ctx.cfg.tol)),
7701        MonadOp::NubSieve => Ok(nub_sieve(y, ctx.cfg.tol, ctx.cfg.rules.lang)),
7702        MonadOp::Unicode { pass_chars } => unicode(y, pass_chars, ctx.cfg.near(), span),
7703        MonadOp::Symbols => to_symbols(y, span),
7704        MonadOp::Words => words(y, span),
7705        MonadOp::LevelOf => Ok(Array::scalar_i64(boxing_level(y))),
7706        MonadOp::MapPaths => Ok(map_paths(y)),
7707        MonadOp::Nest => Ok(nest(y)),
7708        MonadOp::PolyRoots => poly_roots(y, span),
7709        MonadOp::PolyDeriv => poly_deriv(y, span),
7710        MonadOp::AnagramIndex => anagram_index(y, ctx.cfg.rules, span),
7711        MonadOp::CycleForm => cycle_form(y, ctx.cfg.near(), span),
7712        MonadOp::Split => Ok(split_items(y)),
7713        MonadOp::Execute { apl } => execute(y, apl, ctx, span),
7714        MonadOp::NotYet(what) => Err(Error::not_yet(what, span)),
7715        MonadOp::None => {
7716            Err(Error::domain(format!("{} has no monadic meaning", p.name), span))
7717        }
7718    }
7719}
7720
7721/// Left argument of reshape/take/drop: a scalar or vector of integers.
7722/// J `+. y` and `*. y` at rank 0: one complex value as its two parts, so
7723/// the rank machinery turns them into a new trailing axis of length 2.
7724fn complex_parts(y: &Array, polar: bool, span: Span) -> Result<Array> {
7725    let Some(v) = y.to_complex_vec() else {
7726        return Err(wrong_type(y.dtype(), span));
7727    };
7728    let z = v.first().copied().unwrap_or(cx::ZERO);
7729    let pair = if polar { vec![cx::abs(z), cx::arg(z)] } else { vec![z[0], z[1]] };
7730    Ok(Array::from_f64(pair))
7731}
7732
7733fn axis_counts(x: &Array, what: &str, near: NearInt, span: Span) -> Result<Vec<i64>> {
7734    if x.rank() > 1 {
7735        return Err(Error::new(
7736            ErrorKind::Rank,
7737            format!("{what} needs a scalar or vector left argument"),
7738            Some(span),
7739        ));
7740    }
7741    // An empty left argument asks for no axes at all, whatever type it
7742    // happens to carry: `'' $ y` is y's first item, not a type error.
7743    if x.count() == 0 {
7744        return Ok(Vec::new());
7745    }
7746    x.to_i64_vec_near(near)
7747        .ok_or_else(|| Error::domain(format!("{what} needs integer lengths"), span))
7748}
7749
7750/// `x $ y` and `x ⍴ y` are not the same verb.
7751///
7752/// J lays out ITEMS: the result's shape is x followed by the shape of an
7753/// item of y, and the items are reused cyclically, so `$ 3 $ i. 3 4` is
7754/// `3 4` and `'' $ y` is y's first item. APL lays out ELEMENTS: the shape
7755/// is exactly x and y's ravel is reused. The two agree on every vector y,
7756/// which is why the difference shows only above rank 1.
7757///
7758/// An empty y parts them too: J refuses to invent items it was not given,
7759/// and APL fills with the type's fill element.
7760fn reshape(
7761    x: &Array,
7762    y: &Array,
7763    by_items: bool,
7764    apl: bool,
7765    near: NearInt,
7766    span: Span,
7767) -> Result<Array> {
7768    let dims = axis_counts(x, "reshape", near, span)?;
7769    if dims.iter().any(|&d| d < 0) {
7770        return Err(Error::domain("reshape lengths must be nonnegative", span));
7771    }
7772    let mut shape: Vec<usize> = dims.iter().map(|&d| d as usize).collect();
7773    // An item of a scalar is the scalar itself, and a scalar has one item.
7774    let (unit, src) = if by_items {
7775        let item_shape = if y.rank() == 0 { &[][..] } else { &y.shape[1..] };
7776        shape.extend_from_slice(item_shape);
7777        (item_shape.iter().product::<usize>(), y.items().max(usize::from(y.rank() == 0)))
7778    } else {
7779        (1, y.count())
7780    };
7781    let n = crate::limits::elements(&shape, span)?;
7782    let mut data = Data::empty(y.dtype());
7783    if n > 0 && src == 0 {
7784        if by_items {
7785            return Err(Error::new(ErrorKind::Length, "reshape of an empty array", Some(span)));
7786        }
7787        let fill = if apl { prototype_of(y) } else { None };
7788        let mut data = Data::empty(y.dtype());
7789        for _ in 0..n {
7790            push_gap(&mut data, &fill);
7791        }
7792        return Ok(Array::new(shape, data));
7793    }
7794    // Element i of the result is element `i % unit` of item
7795    // `(i / unit) % src`; with `unit` 1 that is the plain cyclic ravel.
7796    // Below `unit * src` the item index never wraps and that element is
7797    // element i itself, so a result the argument's own elements cover is a
7798    // change of shape and nothing else: the buffer comes through shared.
7799    if y.is_row_major() && n <= unit.saturating_mul(src) && n <= y.data.len() {
7800        return Ok(keep_proto(Array::new(shape, y.data.slice(0, n)), y, apl));
7801    }
7802    for i in 0..n {
7803        push_elem(&mut data, &y.data, (i / unit) % src * unit + i % unit);
7804    }
7805    Ok(keep_proto(Array::new(shape, data), y, apl))
7806}
7807
7808/// A take or drop that only touches the leading axis moves a run of whole
7809/// items, which is a slice of the buffer rather than an element-by-element
7810/// walk. `keep` is the items to end up with, `from` the first of them.
7811fn leading_run(y: &Array, counts: &[i64], drop: bool) -> Option<Array> {
7812    if y.rank() == 0 || counts.is_empty() {
7813        return None;
7814    }
7815    // The fast path holds only while every count after the first leaves its
7816    // axis alone. A drop of nothing is a zero; a take of everything is the
7817    // axis's own length, since a take of zero empties the axis instead.
7818    let trailing_untouched = counts[1..].iter().enumerate().all(|(a, &c)| {
7819        if drop { c == 0 } else { c.unsigned_abs() as usize == y.shape[a + 1] }
7820    });
7821    if !trailing_untouched {
7822        return None;
7823    }
7824    let n = y.items();
7825    let k = counts[0];
7826    let a = k.unsigned_abs() as usize;
7827    let (lo, keep) = if drop {
7828        let a = a.min(n);
7829        if k >= 0 { (a, n - a) } else { (0, n - a) }
7830    } else {
7831        // An overtake has to produce fills, which is not a slice.
7832        if a > n {
7833            return None;
7834        }
7835        if k >= 0 { (0, a) } else { (n - a, a) }
7836    };
7837    Some(section(y, lo, lo + keep))
7838}
7839
7840/// A count list the argument's rank cannot take. APL wants exactly one
7841/// count per axis; J takes fewer and leaves the rest of the axes whole, but
7842/// neither language takes more, and only a SCALAR right argument stretches
7843/// to whatever rank the list asks for.
7844fn count_rank(verb: &str, counts: usize, rank: usize, span: Span) -> Error {
7845    Error::new(
7846        ErrorKind::Length,
7847        format!("{counts} {verb} counts for a rank-{rank} argument"),
7848        Some(span),
7849    )
7850}
7851
7852fn take(
7853    x: &Array,
7854    y: &Array,
7855    prototype_fill: bool,
7856    apl: bool,
7857    near: NearInt,
7858    span: Span,
7859) -> Result<Array> {
7860    let counts = axis_counts(x, "take", near, span)?;
7861    // APL overtakes a nested array with the PROTOTYPE of its first item —
7862    // that item's shape, with a zero for every number and a blank for every
7863    // character. J fills with the empty box instead.
7864    let fill = if prototype_fill { prototype_of(y) } else { None };
7865    let promoted;
7866    // A scalar right argument is treated as a one-item array of whatever
7867    // rank the count list asks for: `1 2 {. 5` is a 1 by 2 table.
7868    let base = if y.rank() == 0 {
7869        promoted = Array::new(vec![1; counts.len()], y.data.clone());
7870        &promoted
7871    } else {
7872        y
7873    };
7874    // J's take, unlike its drop, wants at least one count.
7875    let wrong = if apl {
7876        counts.len() != base.rank()
7877    } else {
7878        counts.len() > base.rank() || (counts.is_empty() && base.rank() > 0)
7879    };
7880    if wrong {
7881        return Err(count_rank("take", counts.len(), base.rank(), span));
7882    }
7883    if let Some(run) = leading_run(base, &counts, false) {
7884        return Ok(keep_proto(run, base, prototype_fill));
7885    }
7886    let mut out_shape = base.shape.clone();
7887    for (a, &k) in counts.iter().enumerate() {
7888        out_shape[a] = k.unsigned_abs() as usize;
7889    }
7890    let n = crate::limits::elements(&out_shape, span)?;
7891    let st = strides(&base.shape);
7892    let mut data = Data::empty(base.dtype());
7893    let mut coord = vec![0usize; out_shape.len()];
7894    for _ in 0..n {
7895        let mut idx = 0usize;
7896        let mut inside = true;
7897        for a in 0..out_shape.len() {
7898            let len = base.shape[a] as i64;
7899            let c = coord[a] as i64;
7900            // Positive takes from the front and overtakes at the back;
7901            // negative takes from the back and overtakes at the front.
7902            let s = match counts.get(a) {
7903                Some(&k) if k < 0 => c + len - k.unsigned_abs() as i64,
7904                _ => c,
7905            };
7906            if s < 0 || s >= len {
7907                inside = false;
7908                break;
7909            }
7910            idx += s as usize * st[a];
7911        }
7912        if inside {
7913            push_elem(&mut data, &base.data, idx);
7914        } else if let (Data::Box(v), Some(p)) = (&mut data, &fill) {
7915            v.push(p.clone());
7916        } else {
7917            data.push_fill();
7918        }
7919        odometer(&mut coord, &out_shape);
7920    }
7921    Ok(keep_proto(Array::new(out_shape, data), base, prototype_fill))
7922}
7923
7924/// APL's prototype of a nested array: the first item's own shape, with a
7925/// zero where it holds a number and a blank where it holds a character,
7926/// and the same done to each of its items where it is nested itself.
7927fn prototype_of(y: &Array) -> Option<Array> {
7928    fn zeroed(a: &Array) -> Array {
7929        if let Some(items) = a.as_boxes() {
7930            let inner: Vec<Array> = items.iter().map(zeroed).collect();
7931            return Array::new(a.shape.clone(), Data::Box(inner.into()));
7932        }
7933        let dtype = match a.dtype() {
7934            DType::Char | DType::Symbol => a.dtype(),
7935            _ => DType::I64,
7936        };
7937        Array::new(a.shape.clone(), fill_data(dtype, a.count()))
7938    }
7939    match y.as_boxes()?.first() {
7940        Some(first) => Some(zeroed(first)),
7941        // No item to take one from: an empty nested array remembers what
7942        // its items looked like, and that is already a prototype.
7943        None => y.proto().cloned(),
7944    }
7945}
7946
7947/// An empty nested result remembers the prototype of the array it was made
7948/// from, so that a later fill, reshape or `↑` can answer with it rather
7949/// than with a bare empty box. A simple array's type already says what its
7950/// fills are, and J fills a nested one with the empty box whatever it held,
7951/// so only APL sets this.
7952fn keep_proto(out: Array, src: &Array, apl: bool) -> Array {
7953    if !apl || out.count() > 0 || out.dtype() != DType::Box {
7954        return out;
7955    }
7956    match prototype_of(src) {
7957        Some(p) => out.with_proto(p),
7958        None => out,
7959    }
7960}
7961
7962/// Write one element of fill: the prototype where the caller worked one out
7963/// and the array is nested, and the type's own fill otherwise.
7964fn push_gap(data: &mut Data, fill: &Option<Array>) {
7965    match (data, fill) {
7966        (Data::Box(v), Some(p)) => v.push(p.clone()),
7967        (d, _) => d.push_fill(),
7968    }
7969}
7970
7971fn drop_(x: &Array, y: &Array, apl: bool, near: NearInt, span: Span) -> Result<Array> {
7972    let counts = axis_counts(x, "drop", near, span)?;
7973    let promoted;
7974    let base = if y.rank() == 0 {
7975        promoted = Array::new(vec![1; counts.len()], y.data.clone());
7976        &promoted
7977    } else {
7978        y
7979    };
7980    let wrong =
7981        if apl { counts.len() != base.rank() } else { counts.len() > base.rank() };
7982    if wrong {
7983        return Err(count_rank("drop", counts.len(), base.rank(), span));
7984    }
7985    if let Some(run) = leading_run(base, &counts, true) {
7986        return Ok(keep_proto(run, base, apl));
7987    }
7988    let mut out_shape = base.shape.clone();
7989    let mut offset = vec![0usize; base.rank()];
7990    for (a, &k) in counts.iter().enumerate() {
7991        let len = base.shape[a];
7992        let d = (k.unsigned_abs() as usize).min(len);
7993        out_shape[a] = len - d;
7994        if k > 0 {
7995            offset[a] = d;
7996        }
7997    }
7998    let n: usize = out_shape.iter().product();
7999    let st = strides(&base.shape);
8000    let mut data = Data::empty(base.dtype());
8001    let mut coord = vec![0usize; out_shape.len()];
8002    for _ in 0..n {
8003        let idx: usize = (0..out_shape.len()).map(|a| (coord[a] + offset[a]) * st[a]).sum();
8004        push_elem(&mut data, &base.data, idx);
8005        odometer(&mut coord, &out_shape);
8006    }
8007    Ok(keep_proto(Array::new(out_shape, data), base, apl))
8008}
8009
8010/// Dyadic meaning of a primitive, applied to one pair of cells.
8011fn dyad_op(p: &Prim, x: &Array, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
8012    let apl = cfg.rules.lang == crate::Lang::Apl;
8013    let out = dyad_op_inner(p, x, y, cfg, span);
8014    if apl { out.map(tightened_mixed) } else { out }
8015}
8016
8017/// Every APL result passes through [`tightened_mixed`] on the way out, so
8018/// the mixed simple form never outlives the mixture that called for it.
8019fn dyad_op_inner(p: &Prim, x: &Array, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
8020    let tol = cfg.tol;
8021    let apl = cfg.rules.lang == crate::Lang::Apl;
8022    match p.dyad {
8023        // Reached only when a scalar verb is given non-zero cell ranks; the
8024        // cells then agree among themselves.
8025        DyadOp::Scalar(op) => scalar_dyad(op, x, y, cfg, span),
8026        DyadOp::Reshape => {
8027            let apl = cfg.rules.lang == crate::Lang::Apl;
8028            reshape(x, y, cfg.agreement == Agreement::LeadingPrefix, apl, cfg.near(), span)
8029        }
8030        DyadOp::Take => {
8031            let apl = cfg.rules.lang == crate::Lang::Apl;
8032            take(x, y, cfg.agreement == Agreement::ExactOrScalar, apl, cfg.near(), span)
8033        }
8034        DyadOp::Drop => drop_(x, y, cfg.rules.lang == crate::Lang::Apl, cfg.near(), span),
8035        DyadOp::Right => Ok(y.clone()),
8036        DyadOp::Left => Ok(x.clone()),
8037        DyadOp::Rotate => rotate(x, y, cfg.near(), span),
8038        DyadOp::RotateApl { last } => rotate_apl(x, y, last, cfg.near(), span),
8039        // Only J fills a ragged catenation; APL's conformability rule
8040        // refuses it, as the reference does.
8041        DyadOp::AppendLeading => {
8042            catenate(x, y, true, cfg.agreement == Agreement::LeadingPrefix, span)
8043        }
8044        DyadOp::AppendLast => {
8045            catenate(x, y, false, cfg.agreement == Agreement::LeadingPrefix, span)
8046        }
8047        DyadOp::IndexOf { origin, vector_left } => {
8048            let (x, y) = align_mixed(x, y, apl);
8049            index_of(&x, &y, origin, vector_left, tol, span)
8050        }
8051        DyadOp::MemberJ => Ok(member_j(x, y, tol)),
8052        DyadOp::MemberApl => {
8053            let (x, y) = align_mixed(x, y, apl);
8054            Ok(member_apl(&x, &y, tol))
8055        }
8056        DyadOp::From => from_index(x, y, cfg.near(), span),
8057        DyadOp::Match => {
8058            // APL tells an empty CHARACTER array from an empty numeric one
8059            // — their prototypes differ — where J's `-:` reads only the
8060            // shape once there is nothing left to compare.
8061            let empties_differ = cfg.rules.lang == crate::Lang::Apl
8062                && x.count() == 0
8063                && y.count() == 0
8064                && (x.dtype() == DType::Char) != (y.dtype() == DType::Char);
8065            Ok(Array::scalar_bool(!empties_differ && arrays_match(x, y, tol)))
8066        }
8067        DyadOp::NotMatch => Ok(Array::scalar_bool(!arrays_match(x, y, tol))),
8068        DyadOp::GradeSelect { down } => grade_select(x, y, down, cfg.rules, cfg.tol, span),
8069        DyadOp::Copy => {
8070            copy_items(x, y, cfg.agreement == Agreement::ExactOrScalar, cfg.near(), span)
8071        }
8072        DyadOp::CollateGrade { down, origin } => collate_grade(x, y, down, origin, span),
8073        DyadOp::TransposeJ => transpose_j(x, y, cfg.near(), span),
8074        DyadOp::TransposeApl => transpose_apl(x, y, cfg.rules.origin, cfg.near(), span),
8075        DyadOp::DecodeApl => decode_apl(x, y, span).map(|r| carry_exact2(r, x, y)),
8076        DyadOp::EncodeApl => {
8077            // Dyalog takes the digits exactly, so the tolerance the rest of
8078            // the sentence runs under is set aside for this one reading.
8079            let tol = match cfg.rules.encode_digits {
8080                EncodeDigits::Tolerant => cfg.tol,
8081                EncodeDigits::Exact => Tol { ct: 0.0, ..cfg.tol },
8082            };
8083            encode_apl(x, y, tol, span).map(|r| carry_exact2(r, x, y))
8084        }
8085        DyadOp::Decode => decode(Some(x), y, cfg.tol, span).map(|r| carry_exact2(r, x, y)),
8086        DyadOp::Encode => encode(x, y, cfg.tol, span).map(|r| carry_exact2(r, x, y)),
8087        DyadOp::Laminate => laminate(x, y, span),
8088        DyadOp::Link => link(x, y, span),
8089        DyadOp::Strand => strand(x, y, span),
8090        DyadOp::IntervalIndex { offset, closed } => {
8091            interval_index(x, y, offset, closed, tol, Grading::of(cfg.rules, tol), span)
8092        }
8093        DyadOp::IndexOfLast { origin } => Ok(index_of_last(x, y, origin, tol)),
8094        DyadOp::MatrixDivide => matrix_divide(x, y, span),
8095        DyadOp::PartitionEnclose => partition_enclose(x, y, cfg.near(), span),
8096        DyadOp::PartitionCounts => partition_counts(x, y, cfg.near(), span),
8097        DyadOp::Squad { origin, leading } => squad(x, y, origin, leading, cfg.near(), span),
8098        DyadOp::SelectAxis { axis, rank, origin } => {
8099            select_axis(x, y, axis, rank, origin, cfg.near(), span)
8100        }
8101        DyadOp::Fetch => fetch(x, y, cfg.near(), span),
8102        DyadOp::PolyEval => poly_eval(x, y, span),
8103        DyadOp::PolyIntegral => poly_integral(x, y, span),
8104        DyadOp::TruthTable(m) => truth_table(m, x, y, span),
8105        DyadOp::FormatSpec => format_spec(x, y, &cfg.fmt, span),
8106        DyadOp::FormatSpecJ => format_spec_j(x, y, &cfg.fmt, span),
8107        DyadOp::ParseNumbers => parse_numbers(x, y, span),
8108        DyadOp::SequentialMachine => sequential_machine(x, y, span),
8109        DyadOp::Deal { origin, fixed } => deal(x, y, origin, fixed, cfg.near(), span),
8110        DyadOp::ExactForm => exact_form(x, y, cfg.near(), span),
8111        DyadOp::Boolean(op) => bool_dyad(op, x, y, cfg, span),
8112        DyadOp::Less => {
8113            set_rank(cfg, "without", x, y, span)?;
8114            let (x, y) = align_mixed(x, y, apl);
8115            Ok(set_less(&x, &y, tol))
8116        }
8117        DyadOp::Union => {
8118            set_rank(cfg, "union", x, y, span)?;
8119            let (x, y) = align_mixed(x, y, apl);
8120            union_items(&x, &y, tol, span)
8121        }
8122        DyadOp::Intersect => {
8123            set_rank(cfg, "intersection", x, y, span)?;
8124            let (x, y) = align_mixed(x, y, apl);
8125            Ok(intersect_items(&x, &y, tol))
8126        }
8127        DyadOp::AnagramFrom => anagram_from(x, y, cfg.near(), span),
8128        DyadOp::Permute => permute(x, y, cfg.near(), span),
8129        DyadOp::FindSeq => {
8130            let (x, y) = align_mixed(x, y, apl);
8131            find_seq(&x, &y, tol, apl, span)
8132        }
8133        DyadOp::UnicodeForm => unicode_form(x, y, cfg.near(), span),
8134        DyadOp::SymbolForm => symbol_form(x, y, span),
8135        DyadOp::SparseForm => sparse_form(x, y, cfg.near(), span),
8136        DyadOp::PrimeMeta => prime_meta(x, y, cfg.near(), span).map(|r| carry_exact2(r, x, y)),
8137        DyadOp::PrimeExponents => {
8138            prime_exponents(x, y, cfg.near(), span).map(|r| carry_exact2(r, x, y))
8139        }
8140        DyadOp::Pick { origin } => pick(x, y, origin, cfg.near(), span),
8141        DyadOp::Expand => expand(x, y, cfg.rules.lang == crate::Lang::Apl, cfg.near(), span),
8142        // Writing needs the output sink, which this dispatcher does not
8143        // carry; `dyad_cell` takes it before the call gets here.
8144        DyadOp::WriteStream => Err(Error::internal("1!:2 reached the pure dyad dispatcher")),
8145        DyadOp::NotYet(what) => Err(Error::not_yet(what, span)),
8146        DyadOp::None => Err(Error::domain(format!("{} has no dyadic meaning", p.name), span)),
8147    }
8148}
8149
8150// ------------------------------------------------------------- reduction
8151
8152/// The extreme APL reduces an empty `⌈` or `⌊` to.
8153///
8154/// The language has no infinity in its identities, and the reference does
8155/// not answer the exact largest double either: `⌈/⍬` is this number to
8156/// every digit GNU APL will show of it, and arithmetic on the answer
8157/// confirms the rest. J's identities are the infinities and stay so.
8158const APL_EXTREME: f64 = 1.7976e308;
8159
8160/// The neutral cell of a reduction over no items, if the verb has one.
8161///
8162/// The values are the ones the references produce — both of them, for every
8163/// verb both spell (`x %: y` is J's alone). Where a table entry is
8164/// conventional rather than algebraic (a comparison has no true identity)
8165/// J and GNU APL still agree on it, so libjay follows. `⌊` and `⌈` are the
8166/// one place the two references part: J's neutral cells are the infinities
8167/// and APL's are the extremes of the representable range, so the table
8168/// reads the language.
8169fn reduce_identity(v: &Verb, n: usize, lang: crate::Lang) -> Option<Data> {
8170    let Verb::Prim(p) = v else { return None };
8171    let DyadOp::Scalar(op) = p.dyad else { return None };
8172    let ints = |k: i64| Data::I64(vec![k; n].into());
8173    let bits = |k: u8| Data::Bool(vec![k; n].into());
8174    let extreme =
8175        |sign: f64| Data::F64(vec![sign * if lang == crate::Lang::Apl { APL_EXTREME } else { f64::INFINITY }; n].into());
8176    Some(match op {
8177        ScalarDyad::Add | ScalarDyad::Sub | ScalarDyad::Gcd | ScalarDyad::Residue => ints(0),
8178        ScalarDyad::Mul
8179        | ScalarDyad::DivJ
8180        | ScalarDyad::DivApl
8181        | ScalarDyad::Pow
8182        | ScalarDyad::Lcm
8183        | ScalarDyad::Root
8184        | ScalarDyad::Binomial => ints(1),
8185        ScalarDyad::Min => extreme(1.0),
8186        ScalarDyad::Max => extreme(-1.0),
8187        ScalarDyad::Eq | ScalarDyad::Le | ScalarDyad::Ge => bits(1),
8188        ScalarDyad::Ne | ScalarDyad::Lt | ScalarDyad::Gt => bits(0),
8189        // `j.` and `r.` build a complex number out of two reals; neither
8190        // reference gives them an identity element.
8191        ScalarDyad::MakeComplex | ScalarDyad::PolarBy => return None,
8192        // Logarithm and the circle functions have none: both references
8193        // refuse an empty reduction of them.
8194        ScalarDyad::Log | ScalarDyad::Circle => return None,
8195    })
8196}
8197
8198/// Of the operations the typed fold covers, the ones whose reduction may be
8199/// regrouped: folding the items in chunks and combining the chunks gives the
8200/// same result, exactly for integers and to within the tolerance the float
8201/// contract allows (§5.9). LCM and GCD associate too but reduce through the
8202/// general path, which carries their type rules.
8203fn is_associative(op: ScalarDyad) -> bool {
8204    use ScalarDyad::*;
8205    matches!(op, Add | Mul | Min | Max)
8206}
8207
8208#[inline(always)]
8209fn fold_range_body<S, T, F>(
8210    v: &[S],
8211    m: usize,
8212    lo: usize,
8213    hi: usize,
8214    j0: usize,
8215    acc: &mut [T],
8216    step: &F,
8217) -> bool
8218where
8219    S: Widen<T>,
8220    T: Copy,
8221    F: Fn(T, T) -> (T, bool),
8222{
8223    let w = acc.len();
8224    let base = (hi - 1) * m + j0;
8225    for (slot, &x) in acc.iter_mut().zip(&v[base..base + w]) {
8226        *slot = x.widen();
8227    }
8228    // Overflow is folded into a flag rather than breaking the loop: the
8229    // whole reduction is redone by the general path either way.
8230    let mut over = false;
8231    for i in (lo..hi - 1).rev() {
8232        let row = &v[i * m + j0..i * m + j0 + w];
8233        for (slot, &x) in acc.iter_mut().zip(row) {
8234            let (r, o) = step(x.widen(), *slot);
8235            *slot = r;
8236            over |= o;
8237        }
8238    }
8239    !over
8240}
8241
8242multiversioned! {
8243    #[allow(clippy::too_many_arguments)]
8244    fn fold_range_vectorised[S: Widen<T>, T: Copy, F: Fn(T, T) -> (T, bool)](
8245        v: &[S],
8246        m: usize,
8247        lo: usize,
8248        hi: usize,
8249        j0: usize,
8250        acc: &mut [T],
8251        step: &F,
8252    ) -> bool = fold_range_body;
8253}
8254
8255/// Columns per fold below which the baseline compilation wins.
8256///
8257/// The only loop a wider vector can widen here is the one across an item's
8258/// columns, and a loop of a few columns spends more on entering the vector
8259/// body than the width gives back. Measured on `+/ m` over 20M f64 on one
8260/// thread: at 4 and 8 columns the AVX2 clone is about 1.5x slower than the
8261/// baseline one, at 16 columns and above it is 1.2x to 1.6x faster.
8262const VECTOR_COLUMNS: usize = 16;
8263
8264/// Fold items `lo .. hi` into `acc`, right to left, taking only the columns
8265/// that start at `j0` — `acc.len()` of them. False when a step left the
8266/// element type; the accumulator is then meaningless.
8267///
8268/// Wide enough, and this is the reduce that vectorises, so it runs the
8269/// compilation the CPU is entitled to; narrow, and it runs the baseline one.
8270/// Either way the fold order is the same: the columns are independent
8271/// accumulators, not a reassociation of one.
8272///
8273/// The buffer is read in its own element type and promoted into the
8274/// accumulator's where each element is read, so a narrower argument costs
8275/// no widened copy.
8276#[allow(clippy::too_many_arguments)]
8277#[inline]
8278fn fold_range<S, T, F>(
8279    v: &[S],
8280    m: usize,
8281    lo: usize,
8282    hi: usize,
8283    j0: usize,
8284    acc: &mut [T],
8285    step: &F,
8286) -> bool
8287where
8288    S: Widen<T>,
8289    T: Copy,
8290    F: Fn(T, T) -> (T, bool),
8291{
8292    if acc.len() < VECTOR_COLUMNS {
8293        fold_range_body(v, m, lo, hi, j0, acc, step)
8294    } else {
8295        fold_range_vectorised(v, m, lo, hi, j0, acc, step)
8296    }
8297}
8298
8299/// Independent accumulators an associative fold over a flat run keeps in
8300/// flight at once.
8301///
8302/// One accumulator makes the fold a chain of dependent steps — a float add
8303/// is four cycles on this class of machine, and nothing else can start
8304/// until it retires — so the loop waits on latency and leaves both the
8305/// pipeline and the vector registers idle. Lanes break the chain into
8306/// independent ones and give the autovectoriser a shape it can widen: lane
8307/// `j` takes every eighth element, which is a contiguous vector load.
8308/// Eight is two AVX2 registers of f64 and four of the complex pair.
8309const FOLD_LANES: usize = 8;
8310
8311/// Elements below which a flat fold keeps its plain single accumulator.
8312///
8313/// Below this the lanes cost more to set up and combine than the width
8314/// gives back, and a short fold keeps exactly the rounding it always had.
8315const MIN_LANE_WORK: usize = 8 * FOLD_LANES;
8316
8317/// Fold a flat run right to left with [`FOLD_LANES`] accumulators, the
8318/// lanes combined right to left at the end and the leading remainder folded
8319/// into the result last — so the fold is a regrouping of the sequential one,
8320/// which only an associative step may take (§5.9).
8321#[inline(always)]
8322fn fold_lanes_body<S, T, F>(v: &[S], step: &F) -> Option<T>
8323where
8324    S: Widen<T>,
8325    T: Copy,
8326    F: Fn(T, T) -> (T, bool),
8327{
8328    let n = v.len();
8329    let mut over = false;
8330    if n < MIN_LANE_WORK {
8331        let mut acc = v[n - 1].widen();
8332        for &x in v[..n - 1].iter().rev() {
8333            let (r, o) = step(x.widen(), acc);
8334            acc = r;
8335            over |= o;
8336        }
8337        return (!over).then_some(acc);
8338    }
8339    // The lanes cover a whole number of rows at the end of the run; `head`
8340    // is what is left over at the front.
8341    let rows = n / FOLD_LANES;
8342    let head = n - rows * FOLD_LANES;
8343    let last = head + (rows - 1) * FOLD_LANES;
8344    let mut acc = [v[last].widen(); FOLD_LANES];
8345    for (slot, &x) in acc.iter_mut().zip(&v[last..last + FOLD_LANES]) {
8346        *slot = x.widen();
8347    }
8348    for r in (0..rows - 1).rev() {
8349        let row = &v[head + r * FOLD_LANES..head + (r + 1) * FOLD_LANES];
8350        for (slot, &x) in acc.iter_mut().zip(row) {
8351            let (r, o) = step(x.widen(), *slot);
8352            *slot = r;
8353            over |= o;
8354        }
8355    }
8356    let mut a = acc[FOLD_LANES - 1];
8357    for &x in acc[..FOLD_LANES - 1].iter().rev() {
8358        let (r, o) = step(x, a);
8359        a = r;
8360        over |= o;
8361    }
8362    for &x in v[..head].iter().rev() {
8363        let (r, o) = step(x.widen(), a);
8364        a = r;
8365        over |= o;
8366    }
8367    (!over).then_some(a)
8368}
8369
8370multiversioned! {
8371    fn fold_lanes_vectorised[S: Widen<T>, T: Copy, F: Fn(T, T) -> (T, bool)](
8372        v: &[S],
8373        step: &F,
8374    ) -> Option<T> = fold_lanes_body;
8375}
8376
8377/// A flat run folded with lanes where they pay and with one accumulator
8378/// where they do not.
8379#[inline]
8380fn fold_lanes<S, T, F>(v: &[S], step: &F) -> Option<T>
8381where
8382    S: Widen<T>,
8383    T: Copy,
8384    F: Fn(T, T) -> (T, bool),
8385{
8386    if v.len() < MIN_LANE_WORK {
8387        fold_lanes_body(v, step)
8388    } else {
8389        fold_lanes_vectorised(v, step)
8390    }
8391}
8392
8393/// Fold `n` single-element items, right to left. Associative steps fold in
8394/// chunks on several threads, and in lanes within a chunk.
8395fn fold_flat<S, T, F>(v: &[S], n: usize, assoc: bool, step: &F) -> Option<T>
8396where
8397    S: Widen<T>,
8398    T: Copy + Send + Sync,
8399    F: Fn(T, T) -> (T, bool) + Sync + Send,
8400{
8401    if assoc {
8402        return par::try_fold_chunks(
8403            &v[..n],
8404            |part| fold_lanes(part, step),
8405            |a, b| {
8406                let (r, o) = step(a, b);
8407                (!o).then_some(r)
8408            },
8409        );
8410    }
8411    let mut acc = v[n - 1].widen();
8412    let mut over = false;
8413    for &x in v[..n - 1].iter().rev() {
8414        let (r, o) = step(x.widen(), acc);
8415        acc = r;
8416        over |= o;
8417    }
8418    (!over).then_some(acc)
8419}
8420
8421/// Fold the `n` items of a flat buffer into one item of `m` elements, right
8422/// to left. None when a step left the element type (integer overflow): the
8423/// caller then re-folds through the general path, which knows how to widen.
8424///
8425/// Three shapes, each yielding what one sequential pass would:
8426/// * a wide item splits into ranges of columns, and every element folds its
8427///   own column in order, so any step at all is safe;
8428/// * a one-element item folds in a register;
8429/// * a narrow item splits into chunks of items, which regroups the fold and
8430///   is taken only for an associative step.
8431fn fold_items<S, T, F>(v: &[S], n: usize, m: usize, assoc: bool, step: F) -> Option<Vec<T>>
8432where
8433    S: Widen<T>,
8434    T: Copy + Default + Send + Sync,
8435    F: Fn(T, T) -> (T, bool) + Sync + Send,
8436{
8437    if m >= par::WIDE_ITEM {
8438        let (out, ok) = par::fill_wide(m, n * m, |j0, acc: &mut [T]| {
8439            fold_range(v, m, 0, n, j0, acc, &step)
8440        });
8441        return ok.then_some(out);
8442    }
8443    if m == 1 {
8444        return fold_flat(v, n, assoc, &step).map(|x| vec![x]);
8445    }
8446    let chunks = if assoc { par::chunks(n, n * m) } else { 1 };
8447    if chunks < 2 {
8448        let mut acc = vec![T::default(); m];
8449        return fold_range(v, m, 0, n, 0, &mut acc, &step).then_some(acc);
8450    }
8451    let per = n.div_ceil(chunks);
8452    let parts = par::map_indexed(n.div_ceil(per), |c| {
8453        let mut acc = vec![T::default(); m];
8454        let ok = fold_range(v, m, c * per, ((c + 1) * per).min(n), 0, &mut acc, &step);
8455        ok.then_some(acc)
8456    });
8457    // The chunk results combine right to left, the order the chunks
8458    // themselves were folded in.
8459    let mut it = parts.into_iter().rev();
8460    let mut acc = it.next()??;
8461    for part in it {
8462        let part = part?;
8463        let mut over = false;
8464        for (slot, &x) in acc.iter_mut().zip(&part) {
8465            let (r, o) = step(x, *slot);
8466            *slot = r;
8467            over |= o;
8468        }
8469        if over {
8470            return None;
8471        }
8472    }
8473    Some(acc)
8474}
8475
8476/// One step of a blockwise float fold, scan or window.
8477///
8478/// A NaN abandons the block, exactly as an integer overflow does, and the
8479/// general path redoes the fold one pair at a time. That is where the
8480/// dialect's rules live — J's `*/ 0 , _` is 0 and its `+/ _ , __` is
8481/// refused, and each of those is an IEEE NaN — so the blockwise form never
8482/// has to carry them, and never answers differently from the plain one. An
8483/// infinity is an ordinary value and stays in the block. Ordinary data
8484/// takes this road once per fold and finds nothing.
8485#[inline(always)]
8486fn block_f64(r: f64) -> (f64, bool) {
8487    (r, r.is_nan())
8488}
8489
8490/// The integer fold, over any buffer whose elements are integers once read:
8491/// an `i64` one, or a boolean one promoted where it is read.
8492fn fold_i64<S: Widen<i64>>(op: ScalarDyad, v: &[S], n: usize, m: usize) -> Option<Vec<i64>> {
8493    use ScalarDyad::*;
8494    let assoc = is_associative(op);
8495    match op {
8496        Add => fold_items(v, n, m, assoc, i64::overflowing_add),
8497        Sub => fold_items(v, n, m, assoc, i64::overflowing_sub),
8498        Mul => fold_items(v, n, m, assoc, i64::overflowing_mul),
8499        Min => fold_items(v, n, m, assoc, |a: i64, b: i64| (a.min(b), false)),
8500        Max => fold_items(v, n, m, assoc, |a: i64, b: i64| (a.max(b), false)),
8501        _ => None,
8502    }
8503}
8504
8505fn fold_cx(op: ScalarDyad, v: &[Cx], n: usize, m: usize) -> Option<Vec<Cx>> {
8506    use ScalarDyad::*;
8507    let assoc = is_associative(op);
8508    match op {
8509        Add => fold_items(v, n, m, assoc, |a: Cx, b: Cx| (cx::add(a, b), false)),
8510        Sub => fold_items(v, n, m, assoc, |a: Cx, b: Cx| (cx::sub(a, b), false)),
8511        Mul => fold_items(v, n, m, assoc, |a: Cx, b: Cx| (cx::mul(a, b), false)),
8512        // Min and Max have no complex meaning; the general path reports it.
8513        _ => None,
8514    }
8515}
8516
8517fn fold_f64(op: ScalarDyad, v: &[f64], n: usize, m: usize) -> Option<Vec<f64>> {
8518    use ScalarDyad::*;
8519    let assoc = is_associative(op);
8520    match op {
8521        Add => fold_items(v, n, m, assoc, |a: f64, b: f64| block_f64(a + b)),
8522        Sub => fold_items(v, n, m, assoc, |a: f64, b: f64| block_f64(a - b)),
8523        Mul => fold_items(v, n, m, assoc, |a: f64, b: f64| block_f64(a * b)),
8524        Min => fold_items(v, n, m, assoc, |a: f64, b: f64| (a.min(b), false)),
8525        Max => fold_items(v, n, m, assoc, |a: f64, b: f64| (a.max(b), false)),
8526        _ => None,
8527    }
8528}
8529
8530/// Reduce a numeric buffer with one of the arithmetic operations, without
8531/// an intermediate array per step. None means this path does not apply and
8532/// the general fold must run.
8533fn reduce_typed(op: ScalarDyad, d: &Data, n: usize, m: usize) -> Option<Data> {
8534    use ScalarDyad::*;
8535    // The rest — comparisons, LCM/GCD, the float-only divisions — decide
8536    // their result type by rules the general path already carries.
8537    if !matches!(op, Add | Sub | Mul | Min | Max) {
8538        return None;
8539    }
8540    match d {
8541        Data::F64(v) => Some(Data::F64(fold_f64(op, v, n, m)?.into())),
8542        Data::Complex(v) => Some(Data::Complex(fold_cx(op, v, n, m)?.into())),
8543        Data::I64(v) => Some(Data::I64(fold_i64(op, v, n, m)?.into())),
8544        // Booleans reduce as integers, which is what promotion says the
8545        // general path would produce. The promotion happens where the fold
8546        // reads the element, so the boolean buffer is folded where it lies.
8547        Data::Bool(v) => Some(Data::I64(fold_i64(op, v.as_slice(), n, m)?.into())),
8548        // A bignum has no blockwise form: the exact types fold, scan and
8549        // window through the general path, one step at a time.
8550        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
8551    }
8552}
8553
8554/// Fold each run of `m` consecutive elements into one, right to left.
8555///
8556/// This is the reduction of a vector cell, done for every cell of the frame
8557/// at once. Each run is folded on its own, in the order the insert has, so
8558/// no step is regrouped and any operation at all is safe here.
8559#[inline(always)]
8560fn fold_runs_body<S, T, F>(v: &[S], start: usize, m: usize, out: &mut [T], step: &F) -> bool
8561where
8562    S: Widen<T>,
8563    T: Copy,
8564    F: Fn(T, T) -> (T, bool),
8565{
8566    let mut over = false;
8567    for (k, slot) in out.iter_mut().enumerate() {
8568        let run = &v[(start + k) * m..(start + k + 1) * m];
8569        let mut acc = run[m - 1].widen();
8570        for &x in run[..m - 1].iter().rev() {
8571            let (r, o) = step(x.widen(), acc);
8572            acc = r;
8573            over |= o;
8574        }
8575        *slot = acc;
8576    }
8577    !over
8578}
8579
8580multiversioned! {
8581    fn fold_runs_vectorised[S: Widen<T>, T: Copy, F: Fn(T, T) -> (T, bool)](
8582        v: &[S],
8583        start: usize,
8584        m: usize,
8585        out: &mut [T],
8586        step: &F,
8587    ) -> bool = fold_runs_body;
8588}
8589
8590/// One output per run of `m`, in parallel over the runs. None when a step
8591/// left the element type: the general path then runs and knows how to widen.
8592fn fold_runs<S, T, F>(v: &[S], n: usize, m: usize, step: F) -> Option<Vec<T>>
8593where
8594    S: Widen<T>,
8595    T: Copy + Default + Send + Sync,
8596    F: Fn(T, T) -> (T, bool) + Sync + Send,
8597{
8598    // A run is the loop a vector clone would widen, so a short run takes the
8599    // baseline compilation — the rule `VECTOR_COLUMNS` carries for the fold
8600    // across an item's columns, which is the same loop seen sideways.
8601    let wide = m >= VECTOR_COLUMNS;
8602    let (out, ok) = par::fill_wide(n, n * m, |start, part: &mut [T]| {
8603        if wide {
8604            fold_runs_vectorised(v, start, m, part, &step)
8605        } else {
8606            fold_runs_body(v, start, m, part, &step)
8607        }
8608    });
8609    ok.then_some(out)
8610}
8611
8612fn fold_runs_data(op: ScalarDyad, d: &Data, n: usize, m: usize) -> Option<Data> {
8613    use ScalarDyad::*;
8614    match d {
8615        Data::F64(v) => Some(Data::F64(
8616            match op {
8617                Add => fold_runs(v, n, m, |a: f64, b: f64| block_f64(a + b)),
8618                Sub => fold_runs(v, n, m, |a: f64, b: f64| block_f64(a - b)),
8619                Mul => fold_runs(v, n, m, |a: f64, b: f64| block_f64(a * b)),
8620                Min => fold_runs(v, n, m, |a: f64, b: f64| (a.min(b), false)),
8621                Max => fold_runs(v, n, m, |a: f64, b: f64| (a.max(b), false)),
8622                _ => None,
8623            }?
8624            .into(),
8625        )),
8626        Data::I64(v) => Some(Data::I64(fold_runs_i64(op, v.as_slice(), n, m)?.into())),
8627        // Min and Max have no complex meaning; the general path reports it.
8628        Data::Complex(v) => Some(Data::Complex(
8629            match op {
8630                Add => fold_runs(v, n, m, |a: Cx, b: Cx| (cx::add(a, b), false)),
8631                Sub => fold_runs(v, n, m, |a: Cx, b: Cx| (cx::sub(a, b), false)),
8632                Mul => fold_runs(v, n, m, |a: Cx, b: Cx| (cx::mul(a, b), false)),
8633                _ => None,
8634            }?
8635            .into(),
8636        )),
8637        // Booleans reduce as integers, and are promoted where they are read.
8638        Data::Bool(v) => Some(Data::I64(fold_runs_i64(op, v.as_slice(), n, m)?.into())),
8639        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
8640    }
8641}
8642
8643/// The row fold's integer arm, over an `i64` buffer or a boolean one.
8644fn fold_runs_i64<S: Widen<i64>>(op: ScalarDyad, v: &[S], n: usize, m: usize) -> Option<Vec<i64>> {
8645    use ScalarDyad::*;
8646    match op {
8647        Add => fold_runs(v, n, m, i64::overflowing_add),
8648        Sub => fold_runs(v, n, m, i64::overflowing_sub),
8649        Mul => fold_runs(v, n, m, i64::overflowing_mul),
8650        Min => fold_runs(v, n, m, |a: i64, b: i64| (a.min(b), false)),
8651        Max => fold_runs(v, n, m, |a: i64, b: i64| (a.max(b), false)),
8652        _ => None,
8653    }
8654}
8655
8656// ------------------------------------------------- folds over the columns
8657//
8658// A column-major buffer holds each column of the matrix contiguously, so
8659// the two reductions a table is asked for are both cheaper here than they
8660// are over rows: the leading-axis fold is one flat fold per column, and the
8661// row fold is one pass that reads the columns side by side. Neither
8662// regroups anything the row-major path does not already regroup, and
8663// neither materialises the transpose.
8664
8665/// The `runs` runs of `len` elements a buffer holds, as slices.
8666///
8667/// A buffer that arrived as parts — one per column of an imported table —
8668/// hands its parts back, so reading a table column by column never makes
8669/// the join and never copies. Any other buffer is cut into runs, which for
8670/// an owned or borrowed one is free as well.
8671fn run_slices<T: Clone>(b: &Buf<T>, runs: usize, len: usize) -> Vec<&[T]> {
8672    if let Some(parts) = b.parts() && parts.len() == runs && parts.iter().all(|p| p.len() == len) {
8673        return parts.iter().map(Buf::as_slice).collect();
8674    }
8675    let flat = b.as_slice();
8676    (0..runs).map(|c| &flat[c * len..(c + 1) * len]).collect()
8677}
8678
8679/// Fold each of `runs` contiguous runs of `len` elements into one value,
8680/// right to left.
8681///
8682/// A long run takes the flat fold, which keeps several accumulators in
8683/// flight and splits itself across threads; a short one is a run like any
8684/// other and takes the run fold, which parallelises across the runs
8685/// instead. Both fold in the insert's own order, up to the regrouping an
8686/// associative float fold is already allowed (§5.9).
8687fn fold_columns<S, T, F>(cols: &[&[S]], len: usize, assoc: bool, step: F) -> Option<Vec<T>>
8688where
8689    S: Widen<T>,
8690    T: Copy + Default + Send + Sync,
8691    F: Fn(T, T) -> (T, bool) + Sync + Send,
8692{
8693    // A column long enough to split takes the threads for itself, one
8694    // column at a time; a shorter one is folded whole and the split is
8695    // across the columns. Either way each column is folded by the flat
8696    // fold, which keeps its lanes and its contracted regrouping.
8697    if par::worth_it(len) {
8698        let mut out = Vec::with_capacity(cols.len());
8699        for c in cols {
8700            out.push(fold_flat(c, len, assoc, &step)?);
8701        }
8702        return Some(out);
8703    }
8704    let (out, ok) = par::fill_wide(cols.len(), cols.len() * len, |start, part: &mut [T]| {
8705        let mut ok = true;
8706        for (k, slot) in part.iter_mut().enumerate() {
8707            match fold_flat(cols[start + k], len, assoc, &step) {
8708                Some(v) => *slot = v,
8709                None => ok = false,
8710            }
8711        }
8712        ok
8713    });
8714    ok.then_some(out)
8715}
8716
8717/// Fold every column of a column-major buffer, one value per column.
8718fn fold_columns_data(op: ScalarDyad, d: &Data, runs: usize, len: usize) -> Option<Data> {
8719    use ScalarDyad::*;
8720    if !matches!(op, Add | Sub | Mul | Min | Max) {
8721        return None;
8722    }
8723    let assoc = is_associative(op);
8724    macro_rules! by {
8725        ($v:expr, $add:expr, $sub:expr, $mul:expr, $min:expr, $max:expr) => {{
8726            let cols = run_slices($v, runs, len);
8727            match op {
8728                Add => fold_columns(&cols, len, assoc, $add),
8729                Sub => fold_columns(&cols, len, assoc, $sub),
8730                Mul => fold_columns(&cols, len, assoc, $mul),
8731                Min => fold_columns(&cols, len, assoc, $min),
8732                Max => fold_columns(&cols, len, assoc, $max),
8733                _ => None,
8734            }?
8735        }};
8736    }
8737    match d {
8738        Data::F64(v) => Some(Data::F64(
8739            by!(
8740                v,
8741                |a: f64, b: f64| block_f64(a + b),
8742                |a: f64, b: f64| block_f64(a - b),
8743                |a: f64, b: f64| block_f64(a * b),
8744                |a: f64, b: f64| (a.min(b), false),
8745                |a: f64, b: f64| (a.max(b), false)
8746            )
8747            .into(),
8748        )),
8749        Data::I64(v) => Some(Data::I64(
8750            by!(
8751                v,
8752                i64::overflowing_add,
8753                i64::overflowing_sub,
8754                i64::overflowing_mul,
8755                |a: i64, b: i64| (a.min(b), false),
8756                |a: i64, b: i64| (a.max(b), false)
8757            )
8758            .into(),
8759        )),
8760        Data::Complex(v) => {
8761            if !matches!(op, Add | Sub | Mul) {
8762                return None;
8763            }
8764            Some(Data::Complex(
8765                by!(
8766                    v,
8767                    |a: Cx, b: Cx| (cx::add(a, b), false),
8768                    |a: Cx, b: Cx| (cx::sub(a, b), false),
8769                    |a: Cx, b: Cx| (cx::mul(a, b), false),
8770                    |_: Cx, _: Cx| unreachable!("refused above"),
8771                    |_: Cx, _: Cx| unreachable!("refused above")
8772                )
8773                .into(),
8774            ))
8775        }
8776        // Booleans reduce as integers, which is what promotion says the
8777        // general path would produce; the promotion happens where the fold
8778        // reads the element, so the columns are folded where they lie.
8779        Data::Bool(v) => Some(Data::I64(
8780            by!(
8781                v,
8782                i64::overflowing_add,
8783                i64::overflowing_sub,
8784                i64::overflowing_mul,
8785                |a: i64, b: i64| (a.min(b), false),
8786                |a: i64, b: i64| (a.max(b), false)
8787            )
8788            .into(),
8789        )),
8790        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
8791    }
8792}
8793
8794/// `u/ y` over a column-major argument: the leading axis is what each
8795/// contiguous run holds, so every run folds where it lies and no transpose
8796/// is made. None means the verb, the type or the shape is not one this
8797/// covers.
8798fn reduce_columns(v: &Verb, y: &Array) -> Option<Array> {
8799    let Verb::Prim(p) = v else { return None };
8800    let DyadOp::Scalar(op) = p.dyad else { return None };
8801    if !y.dtype().is_numeric() {
8802        return None;
8803    }
8804    let n = y.shape[0];
8805    let m: usize = y.shape[1..].iter().product();
8806    // An empty leading axis reduces to the operation's identity, which the
8807    // general path knows and this one does not.
8808    if n == 0 || m == 0 {
8809        return None;
8810    }
8811    let shape = y.shape[1..].to_vec();
8812    // One item reduces to that item, type and all: the insert never runs.
8813    // The trailing axes lie column-major, which is what the result keeps.
8814    if n == 1 {
8815        return Some(Array::col_major(shape, y.data.clone()));
8816    }
8817    let data = fold_columns_data(op, &y.data, m, n)?;
8818    Some(Array::col_major(shape, data))
8819}
8820
8821/// Fold the rows of a column-major matrix: one pass that reads the columns
8822/// side by side, each row folded right to left in the insert's own order.
8823fn fold_across<S, T, F>(cols: &[&[S]], rows: usize, step: F) -> Option<Vec<T>>
8824where
8825    S: Widen<T>,
8826    T: Copy + Default + Send + Sync,
8827    F: Fn(T, T) -> (T, bool) + Sync + Send,
8828{
8829    let (last, rest) = cols.split_last()?;
8830    let (out, ok) = par::fill(rows, |start, part: &mut [T]| {
8831        let mut over = false;
8832        for (k, slot) in part.iter_mut().enumerate() {
8833            let i = start + k;
8834            let mut acc = last[i].widen();
8835            for c in rest.iter().rev() {
8836                let (r, o) = step(c[i].widen(), acc);
8837                acc = r;
8838                over |= o;
8839            }
8840            *slot = acc;
8841        }
8842        !over
8843    });
8844    ok.then_some(out)
8845}
8846
8847fn fold_across_data(op: ScalarDyad, d: &Data, rows: usize, cols: usize) -> Option<Data> {
8848    use ScalarDyad::*;
8849    if !matches!(op, Add | Sub | Mul | Min | Max) {
8850        return None;
8851    }
8852    macro_rules! by {
8853        ($v:expr, $add:expr, $sub:expr, $mul:expr, $min:expr, $max:expr) => {{
8854            let parts = run_slices($v, cols, rows);
8855            match op {
8856                Add => fold_across(&parts, rows, $add),
8857                Sub => fold_across(&parts, rows, $sub),
8858                Mul => fold_across(&parts, rows, $mul),
8859                Min => fold_across(&parts, rows, $min),
8860                Max => fold_across(&parts, rows, $max),
8861                _ => None,
8862            }?
8863        }};
8864    }
8865    match d {
8866        Data::F64(v) => Some(Data::F64(
8867            by!(
8868                v,
8869                |a: f64, b: f64| block_f64(a + b),
8870                |a: f64, b: f64| block_f64(a - b),
8871                |a: f64, b: f64| block_f64(a * b),
8872                |a: f64, b: f64| (a.min(b), false),
8873                |a: f64, b: f64| (a.max(b), false)
8874            )
8875            .into(),
8876        )),
8877        Data::I64(v) => Some(Data::I64(
8878            by!(
8879                v,
8880                i64::overflowing_add,
8881                i64::overflowing_sub,
8882                i64::overflowing_mul,
8883                |a: i64, b: i64| (a.min(b), false),
8884                |a: i64, b: i64| (a.max(b), false)
8885            )
8886            .into(),
8887        )),
8888        Data::Complex(v) => {
8889            if !matches!(op, Add | Sub | Mul) {
8890                return None;
8891            }
8892            Some(Data::Complex(
8893                by!(
8894                    v,
8895                    |a: Cx, b: Cx| (cx::add(a, b), false),
8896                    |a: Cx, b: Cx| (cx::sub(a, b), false),
8897                    |a: Cx, b: Cx| (cx::mul(a, b), false),
8898                    |_: Cx, _: Cx| unreachable!("refused above"),
8899                    |_: Cx, _: Cx| unreachable!("refused above")
8900                )
8901                .into(),
8902            ))
8903        }
8904        // Read as integers where each element is read, as everywhere else.
8905        Data::Bool(v) => Some(Data::I64(
8906            by!(
8907                v,
8908                i64::overflowing_add,
8909                i64::overflowing_sub,
8910                i64::overflowing_mul,
8911                |a: i64, b: i64| (a.min(b), false),
8912                |a: i64, b: i64| (a.max(b), false)
8913            )
8914            .into(),
8915        )),
8916        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
8917    }
8918}
8919
8920/// `u/"1 y` over a column-major matrix: every row folded across the
8921/// columns, without the transpose the row-major path would need first.
8922fn reduce_rows_columns(u: &Verb, y: &Array) -> Option<Array> {
8923    let Verb::Reduce(inner) = u else { return None };
8924    let Verb::Prim(p) = &**inner else { return None };
8925    let DyadOp::Scalar(op) = p.dyad else { return None };
8926    // Only a matrix: at higher rank the cells this folds are not the runs
8927    // the buffer holds.
8928    if y.rank() != 2 || !y.dtype().is_numeric() {
8929        return None;
8930    }
8931    let (rows, cols) = (y.shape[0], y.shape[1]);
8932    // An empty cell reduces to the operation's identity, which the general
8933    // path knows and this one does not.
8934    if rows == 0 || cols == 0 {
8935        return None;
8936    }
8937    if cols == 1 {
8938        // A cell of one element reduces to that element, type and all.
8939        return Some(Array::new(vec![rows], y.data.clone()));
8940    }
8941    let data = fold_across_data(op, &y.data, rows, cols)?;
8942    Some(Array::new(vec![rows], data))
8943}
8944
8945/// `u/"1 y` and its like: a reduction whose cells are vectors, answered by
8946/// folding every cell out of the one buffer.
8947///
8948/// The rank machinery would build an array per cell, reduce it, and frame
8949/// the results — three allocations for every row of a matrix. This produces
8950/// exactly what that produces, and reads the buffer once. None means the
8951/// shape, the verb or the type is not one this covers, and the general path
8952/// runs instead.
8953fn reduce_vector_cells(u: &Verb, y: &Array, frame_rank: usize) -> Option<Array> {
8954    let Verb::Reduce(inner) = u else { return None };
8955    let Verb::Prim(p) = &**inner else { return None };
8956    let DyadOp::Scalar(op) = p.dyad else { return None };
8957    // The cell is a vector, so its reduction is a scalar and the result has
8958    // the frame's own shape.
8959    if y.rank() != frame_rank + 1 || !y.dtype().is_numeric() {
8960        return None;
8961    }
8962    let m = y.shape[frame_rank];
8963    // An empty cell reduces to the operation's identity, which the general
8964    // path knows and this one does not.
8965    if m == 0 {
8966        return None;
8967    }
8968    use ScalarDyad::{Add, Max, Min, Mul, Sub};
8969    if !matches!(op, Add | Sub | Mul | Min | Max) {
8970        return None;
8971    }
8972    let frame = y.shape[..frame_rank].to_vec();
8973    if m == 1 {
8974        // A cell of one element reduces to that element, type and all: the
8975        // insert never runs, so nothing widens.
8976        return Some(Array::new(frame, y.data.clone()));
8977    }
8978    let n: usize = frame.iter().product();
8979    let data = fold_runs_data(op, &y.data, n, m)?;
8980    Some(Array::new(frame, data))
8981}
8982
8983/// Insert `v` between the items of `y`, folding right to left.
8984fn reduce(v: &Verb, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
8985    if y.rank() == 0 {
8986        return Ok(y.clone());
8987    }
8988    let n = y.items();
8989    if n == 1 {
8990        return Ok(y.item(0));
8991    }
8992    let cell_shape = y.shape[1..].to_vec();
8993    let m: usize = cell_shape.iter().product();
8994    if n == 0 {
8995        // Catenation's identity is the empty LIST, whatever shape the cells
8996        // that were not there would have had: `,/ i. 0 3` is `i. 0`.
8997        if matches!(v, Verb::Prim(p) if matches!(p.dyad, DyadOp::AppendLeading | DyadOp::AppendLast))
8998        {
8999            return Ok(Array::new(vec![0], Data::empty(y.dtype())));
9000        }
9001        return match reduce_identity(v, m, ctx.cfg.rules.lang) {
9002            Some(d) => Ok(Array::new(cell_shape, d)),
9003            None => Err(Error::domain(
9004                format!("empty reduction has no identity for {}", v.name()),
9005                span,
9006            )),
9007        };
9008    }
9009    if y.dtype().is_numeric() && let Verb::Prim(p) = v && let DyadOp::Scalar(op) = p.dyad {
9010        // The typed fold covers the arithmetic reductions and runs
9011        // in parallel wherever the fold order allows; it declines
9012        // (integer overflow, an operation with its own type rules)
9013        // by returning None, and then the general fold below runs.
9014        if let Some(d) = reduce_typed(op, y.row_major_data(), n, m) {
9015            return Ok(Array::new(cell_shape, d));
9016        }
9017        // Fold over the raw buffer, one whole item per step, without
9018        // materialising item arrays.
9019        let mut acc = y.data.slice((n - 1) * m, n * m);
9020        for i in (0..n - 1).rev() {
9021            acc =
9022                scalar_dyad_data(
9023                    op,
9024                    &y.data,
9025                    i * m,
9026                    1,
9027                    &acc,
9028                    0,
9029                    1,
9030                    m,
9031                    ctx.cfg.tol,
9032                    ctx.cfg.rules,
9033                    span,
9034                )?;
9035        }
9036        return Ok(Array::new(cell_shape, acc));
9037    }
9038    if ctx.cfg.rules.lang == crate::Lang::Apl {
9039        return item_fold(v, y, ctx, span);
9040    }
9041    let mut acc = y.item(n - 1);
9042    for i in (0..n - 1).rev() {
9043        acc = v.dyad(&y.item(i), &acc, ctx, span)?;
9044    }
9045    Ok(acc)
9046}
9047
9048/// The same insert read by items, which is what APL's `f/` and `f⌿` are.
9049///
9050/// J folds whole cells: `,/ 2 3$i.6` catenates the two rows. APL folds the
9051/// ELEMENTS along the reduced axis and leaves the other axes as the frame,
9052/// so `,⌿2 3⍴⍳6` pairs the columns and answers three two-element vectors.
9053/// Each element is disclosed on the way in and the fold's value is enclosed
9054/// on the way out, which is why `,/1 2 3` is an enclosed vector rather than
9055/// a bare one. The arithmetic reductions never reach here: folding atoms
9056/// and folding cells agree for a scalar function, and the typed path above
9057/// keeps them.
9058fn item_fold(v: &Verb, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
9059    let n = y.items();
9060    let frame = y.shape[1..].to_vec();
9061    let m: usize = frame.iter().product();
9062    if m == 0 {
9063        return assemble(&frame, Vec::new(), span);
9064    }
9065    let base = y.to_row_major();
9066    let cells = each_cell(m, base.count(), v.is_pure(), ctx, |p, c| {
9067        let mut acc = open_cell(&atom(&base, (n - 1) * m + p));
9068        for i in (0..n - 1).rev() {
9069            acc = v.dyad(&open_cell(&atom(&base, i * m + p)), &acc, c, span)?;
9070        }
9071        Ok(enclose(&acc, Enclose::ExceptSimpleScalar))
9072    })?;
9073    assemble_items(&frame, cells, span)
9074}
9075
9076// ------------------------------------------------- windows, scans, power
9077
9078/// The elementwise operation a windowed verb folds with, when the verb is
9079/// exactly a reduction by a scalar primitive. The fast paths below apply
9080/// only then: they fold whole items at full rank, which is what `u/` does
9081/// and what any other spelling (a rank wrapper, a train) does not.
9082fn folded_op(u: &Verb) -> Option<ScalarDyad> {
9083    let Verb::Reduce(inner) = u else { return None };
9084    let Verb::Prim(p) = &**inner else { return None };
9085    match p.dyad {
9086        DyadOp::Scalar(op) => Some(op),
9087        _ => None,
9088    }
9089}
9090
9091/// Items `lo .. hi` of `y`, sharing its buffer where the buffer allows.
9092fn section(y: &Array, lo: usize, hi: usize) -> Array {
9093    let m = y.item_size();
9094    let mut shape = y.shape.clone();
9095    shape[0] = hi - lo;
9096    Array::new(shape, y.data.slice(lo * m, hi * m))
9097}
9098
9099/// `y` with a leading axis: a scalar is one item, which is how both
9100/// languages count the items of a rank-0 argument.
9101fn as_items(y: &Array) -> Option<Array> {
9102    (y.rank() == 0).then(|| Array::new(vec![1], y.data.clone()))
9103}
9104
9105#[inline(always)]
9106fn scan_flat_body<S, T, F>(v: &[S], n: usize, m: usize, back: bool, step: F) -> Option<Vec<T>>
9107where
9108    S: Widen<T>,
9109    T: Copy + Default,
9110    F: Fn(T, T) -> (T, bool),
9111{
9112    if m == 1 {
9113        // One element per item is the shape a time series has, and it is
9114        // the one worth keeping the accumulator in a register for.
9115        let mut out = vec![T::default(); n];
9116        let mut over = false;
9117        if back {
9118            let mut acc = v[n - 1].widen();
9119            out[n - 1] = acc;
9120            for (slot, &x) in out[..n - 1].iter_mut().zip(&v[..n - 1]).rev() {
9121                let (r, o) = step(x.widen(), acc);
9122                acc = r;
9123                over |= o;
9124                *slot = acc;
9125            }
9126        } else {
9127            let mut acc = v[0].widen();
9128            out[0] = acc;
9129            for (slot, &x) in out[1..n].iter_mut().zip(&v[1..n]) {
9130                let (r, o) = step(acc, x.widen());
9131                acc = r;
9132                over |= o;
9133                *slot = acc;
9134            }
9135        }
9136        return (!over).then_some(out);
9137    }
9138    let mut out = vec![T::default(); n * m];
9139    let mut acc = vec![T::default(); m];
9140    let mut over = false;
9141    if back {
9142        for (slot, &x) in acc.iter_mut().zip(&v[(n - 1) * m..n * m]) {
9143            *slot = x.widen();
9144        }
9145        out[(n - 1) * m..n * m].copy_from_slice(&acc);
9146        for i in (0..n - 1).rev() {
9147            for (j, slot) in acc.iter_mut().enumerate() {
9148                let (r, o) = step(v[i * m + j].widen(), *slot);
9149                *slot = r;
9150                over |= o;
9151            }
9152            out[i * m..i * m + m].copy_from_slice(&acc);
9153        }
9154    } else {
9155        for (slot, &x) in acc.iter_mut().zip(&v[..m]) {
9156            *slot = x.widen();
9157        }
9158        out[..m].copy_from_slice(&acc);
9159        for i in 1..n {
9160            for (j, slot) in acc.iter_mut().enumerate() {
9161                let (r, o) = step(*slot, v[i * m + j].widen());
9162                *slot = r;
9163                over |= o;
9164            }
9165            out[i * m..i * m + m].copy_from_slice(&acc);
9166        }
9167    }
9168    (!over).then_some(out)
9169}
9170
9171multiversioned! {
9172    fn scan_flat_vectorised[S: Widen<T>, T: Copy + Default, F: Fn(T, T) -> (T, bool)](
9173        v: &[S],
9174        n: usize,
9175        m: usize,
9176        back: bool,
9177        step: F,
9178    ) -> Option<Vec<T>> = scan_flat_body;
9179}
9180
9181/// Running fold over `n` items of `m` elements each, one output item per
9182/// step. Backward is exactly the insert's right-to-left order, so it holds
9183/// for any step; forward is the left-to-right order, which agrees with the
9184/// insert only when the step is associative. None when a step left the
9185/// element type.
9186///
9187/// Only the wide shape has anything to gain from a wider vector, and for
9188/// the same reason the reduce has: the loop that widens is the one across
9189/// an item's elements. A scan of one element per item is a chain of
9190/// dependent steps, which no vector shortens, so it takes the baseline
9191/// compilation.
9192fn scan_flat<S, T, F>(v: &[S], n: usize, m: usize, back: bool, step: F) -> Option<Vec<T>>
9193where
9194    S: Widen<T>,
9195    T: Copy + Default,
9196    F: Fn(T, T) -> (T, bool),
9197{
9198    if m < VECTOR_COLUMNS {
9199        scan_flat_body(v, n, m, back, step)
9200    } else {
9201        scan_flat_vectorised(v, n, m, back, step)
9202    }
9203}
9204
9205fn scan_i64<S: Widen<i64>>(
9206    op: ScalarDyad,
9207    v: &[S],
9208    n: usize,
9209    m: usize,
9210    back: bool,
9211) -> Option<Vec<i64>> {
9212    use ScalarDyad::*;
9213    match op {
9214        Add => scan_flat(v, n, m, back, i64::overflowing_add),
9215        Sub => scan_flat(v, n, m, back, i64::overflowing_sub),
9216        Mul => scan_flat(v, n, m, back, i64::overflowing_mul),
9217        Min => scan_flat(v, n, m, back, |a: i64, b: i64| (a.min(b), false)),
9218        Max => scan_flat(v, n, m, back, |a: i64, b: i64| (a.max(b), false)),
9219        _ => None,
9220    }
9221}
9222
9223fn scan_cx(op: ScalarDyad, v: &[Cx], n: usize, m: usize, back: bool) -> Option<Vec<Cx>> {
9224    use ScalarDyad::*;
9225    match op {
9226        Add => scan_flat(v, n, m, back, |a: Cx, b: Cx| (cx::add(a, b), false)),
9227        Sub => scan_flat(v, n, m, back, |a: Cx, b: Cx| (cx::sub(a, b), false)),
9228        Mul => scan_flat(v, n, m, back, |a: Cx, b: Cx| (cx::mul(a, b), false)),
9229        _ => None,
9230    }
9231}
9232
9233fn scan_f64<S: Widen<f64>>(
9234    op: ScalarDyad,
9235    v: &[S],
9236    n: usize,
9237    m: usize,
9238    back: bool,
9239) -> Option<Vec<f64>> {
9240    use ScalarDyad::*;
9241    match op {
9242        Add => scan_flat(v, n, m, back, |a: f64, b: f64| block_f64(a + b)),
9243        Sub => scan_flat(v, n, m, back, |a: f64, b: f64| block_f64(a - b)),
9244        Mul => scan_flat(v, n, m, back, |a: f64, b: f64| block_f64(a * b)),
9245        Min => scan_flat(v, n, m, back, |a: f64, b: f64| (a.min(b), false)),
9246        Max => scan_flat(v, n, m, back, |a: f64, b: f64| (a.max(b), false)),
9247        _ => None,
9248    }
9249}
9250
9251/// The scan of a numeric buffer in one pass. None means this path does not
9252/// apply. Integer overflow anywhere widens the whole result to float, which
9253/// is what the per-prefix reduction would also produce.
9254fn scan_typed(op: ScalarDyad, d: &Data, n: usize, m: usize, back: bool) -> Option<Data> {
9255    use ScalarDyad::*;
9256    if !matches!(op, Add | Sub | Mul | Min | Max) {
9257        return None;
9258    }
9259    // An integer buffer and a boolean one both scan as integers, each read
9260    // in its own type; the float retry reads the same buffer again rather
9261    // than a widened copy of it.
9262    fn ints<S: Widen<i64> + Widen<f64>>(
9263        op: ScalarDyad,
9264        v: &[S],
9265        n: usize,
9266        m: usize,
9267        back: bool,
9268    ) -> Data {
9269        match scan_i64(op, v, n, m, back) {
9270            Some(out) => Data::I64(out.into()),
9271            None => Data::F64(
9272                scan_f64(op, v, n, m, back).expect("the float scan cannot overflow").into(),
9273            ),
9274        }
9275    }
9276    match d {
9277        Data::F64(v) => Some(Data::F64(scan_f64(op, v.as_slice(), n, m, back)?.into())),
9278        Data::Complex(v) => Some(Data::Complex(scan_cx(op, v, n, m, back)?.into())),
9279        Data::I64(v) => Some(ints(op, v.as_slice(), n, m, back)),
9280        Data::Bool(v) => Some(ints(op, v.as_slice(), n, m, back)),
9281        // A bignum has no blockwise form: the exact types fold, scan and
9282        // window through the general path, one step at a time.
9283        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
9284    }
9285}
9286
9287/// The constant `c` of an affine step `x u y = x + c * y`, when the verb is
9288/// exactly that tree and `c` is a scalar noun written in the source.
9289///
9290/// The two spellings of a first-order recurrence are `[ + c * ]` and its
9291/// mirror `(c * ]) + [`. The match is on the tree, so a verb that computes
9292/// the same thing another way is not one of them and folds the general way.
9293fn affine_step(u: &Verb) -> Option<&Array> {
9294    // The ranks are part of the match: arithmetic pairs atoms and `[` and
9295    // `]` take whole arguments, and a verb wearing any other rank is a
9296    // different verb.
9297    fn prim(v: &Verb, want: DyadOp, ranks: [i64; 3]) -> bool {
9298        matches!(v, Verb::Prim(p) if p.dyad == want && p.ranks == ranks)
9299    }
9300    const ATOMS: [i64; 3] = [0, 0, 0];
9301    const WHOLE: [i64; 3] = [RANK_INF; 3];
9302    // `c * ]`: the accumulator scaled by the constant, and nothing else.
9303    fn scaled(v: &Verb) -> Option<&Array> {
9304        let Verb::NounFork(c, g, h) = v else { return None };
9305        let noun = c.rank() == 0
9306            && matches!(c.dtype(), DType::Bool | DType::I64 | DType::F64 | DType::Complex);
9307        let tree = prim(g, DyadOp::Scalar(ScalarDyad::Mul), ATOMS)
9308            && prim(h, DyadOp::Right, WHOLE);
9309        (noun && tree).then_some(c)
9310    }
9311    let Verb::Fork(f, g, h) = u else { return None };
9312    if !prim(g, DyadOp::Scalar(ScalarDyad::Add), ATOMS) {
9313        return None;
9314    }
9315    if prim(f, DyadOp::Left, WHOLE) {
9316        scaled(h)
9317    } else if prim(h, DyadOp::Left, WHOLE) {
9318        scaled(f)
9319    } else {
9320        None
9321    }
9322}
9323
9324/// The arithmetic a running affine fold needs of its element type, and the
9325/// test that a power of the constant is still a number.
9326struct Ring<T> {
9327    add: fn(T, T) -> T,
9328    mul: fn(T, T) -> T,
9329    one: T,
9330    finite: fn(T) -> bool,
9331}
9332
9333/// A running affine fold: `out[k] = v[k] + c * out[k+1]` backwards, and
9334/// forwards the same series carried the only way one pass can carry it —
9335/// the k-th prefix is the sum of `c^i * v[i]`, so the power of `c` runs
9336/// along with it. None when a power leaves the finite range, which is the
9337/// one case that sum and the fold it stands for do not agree on.
9338fn affine_flat<T>(v: &[T], c: T, n: usize, m: usize, back: bool, r: &Ring<T>) -> Option<Vec<T>>
9339where
9340    T: Copy + Default,
9341{
9342    let (add, mul) = (r.add, r.mul);
9343    let mut out = vec![T::default(); n * m];
9344    if back {
9345        out[(n - 1) * m..].copy_from_slice(&v[(n - 1) * m..n * m]);
9346        for i in (0..n - 1).rev() {
9347            for j in 0..m {
9348                out[i * m + j] = add(v[i * m + j], mul(c, out[(i + 1) * m + j]));
9349            }
9350        }
9351    } else {
9352        out[..m].copy_from_slice(&v[..m]);
9353        let mut pow = r.one;
9354        for i in 1..n {
9355            pow = mul(pow, c);
9356            if !(r.finite)(pow) {
9357                return None;
9358            }
9359            for j in 0..m {
9360                out[i * m + j] = add(out[(i - 1) * m + j], mul(pow, v[i * m + j]));
9361            }
9362        }
9363    }
9364    Some(out)
9365}
9366
9367/// `u/\ y` and `u/\. y` over an affine step, in one pass instead of one
9368/// fold per run.
9369///
9370/// Backwards this is the insert's own order — the steps are the steps the
9371/// general path takes, in the same order, so the answer is the same to the
9372/// last bit. Forwards it is the same series regrouped, which rounds as the
9373/// blocked window fold rounds and not as the insert would. None when the
9374/// types are not the ones that carry it: two integers fold exactly and are
9375/// left alone, as are the exact types.
9376fn affine_scan(c: &Array, y: &Array, back: bool) -> Option<Data> {
9377    let (n, m) = (y.items(), y.item_size());
9378    let machine = |t: DType| matches!(t, DType::Bool | DType::I64 | DType::F64 | DType::Complex);
9379    if n == 0 || !machine(c.dtype()) || !machine(y.dtype()) {
9380        return None;
9381    }
9382    match DType::promote(c.dtype(), y.dtype())? {
9383        DType::F64 => {
9384            let (mut tc, mut tv) = (Vec::new(), Vec::new());
9385            let k = *borrow_f64(&c.data, &mut tc).first()?;
9386            let v = borrow_f64(y.row_major_data(), &mut tv);
9387            let r = Ring { add: |a, b| a + b, mul: |a, b| a * b, one: 1.0, finite: f64::is_finite };
9388            Some(Data::F64(affine_flat(v, k, n, m, back, &r)?.into()))
9389        }
9390        DType::Complex => {
9391            let (mut tc, mut tv) = (Vec::new(), Vec::new());
9392            let k = *borrow_cx(&c.data, &mut tc).first()?;
9393            let v = borrow_cx(y.row_major_data(), &mut tv);
9394            let finite = |z: Cx| z[0].is_finite() && z[1].is_finite();
9395            let r = Ring { add: cx::add, mul: cx::mul, one: [1.0, 0.0], finite };
9396            Some(Data::Complex(affine_flat(v, k, n, m, back, &r)?.into()))
9397        }
9398        _ => None,
9399    }
9400}
9401
9402/// Fold every window of `w` consecutive items into one item.
9403///
9404/// The items are cut into blocks of `w`. Within a block the running folds
9405/// from its start and from its end are computed once each, and then every
9406/// window is either one whole block or one block's suffix combined with the
9407/// next block's prefix. That is two steps per element with no accumulator
9408/// running longer than `w` of them, so the float error of a window is the
9409/// error of computing that window on its own — a cumulative sum over the
9410/// whole argument, differenced, would instead carry the drift of the entire
9411/// series into every window.
9412///
9413/// `step` has to be associative: the grouping is not the insert's own. The
9414/// float reassociation is the §5.9 contract, the same one reduction takes.
9415/// None when a step left the element type.
9416fn window_fold<S, T, F>(v: &[S], n: usize, m: usize, w: usize, step: F) -> Option<Vec<T>>
9417where
9418    S: Widen<T>,
9419    T: Copy + Default + Send + Sync,
9420    F: Fn(T, T) -> (T, bool) + Sync + Send,
9421{
9422    debug_assert!(w >= 1 && n >= w);
9423    if m == 1 {
9424        return window_fold_flat(v, n, w, step);
9425    }
9426    let count = n - w + 1;
9427    let mut out = vec![T::default(); count * m];
9428    // Prefix folds of the current block, suffix folds of it and of the one
9429    // before: `w` items each, whatever the length of the argument.
9430    let mut pre = vec![T::default(); w * m];
9431    let mut suf = vec![T::default(); w * m];
9432    let mut prev = vec![T::default(); w * m];
9433    let mut over = false;
9434    for b in 0..n.div_ceil(w) {
9435        let bs = b * w;
9436        let be = ((b + 1) * w).min(n);
9437        for (slot, &x) in pre[..m].iter_mut().zip(&v[bs * m..bs * m + m]) {
9438            *slot = x.widen();
9439        }
9440        for i in 1..be - bs {
9441            let (o, p) = (i * m, (i - 1) * m);
9442            for j in 0..m {
9443                let (r, f) = step(pre[p + j], v[(bs + i) * m + j].widen());
9444                pre[o + j] = r;
9445                over |= f;
9446            }
9447        }
9448        // Every window whose last item is in this block; its first item is
9449        // either this block's start or somewhere in the block before.
9450        for e in bs.max(w - 1)..be {
9451            let i = e + 1 - w;
9452            let (oo, po) = (i * m, (e - bs) * m);
9453            if i == bs {
9454                out[oo..oo + m].copy_from_slice(&pre[po..po + m]);
9455            } else {
9456                let so = (i + w - bs) * m;
9457                for j in 0..m {
9458                    let (r, f) = step(prev[so + j], pre[po + j]);
9459                    out[oo + j] = r;
9460                    over |= f;
9461                }
9462            }
9463        }
9464        let last = be - 1 - bs;
9465        for (slot, &x) in suf[last * m..last * m + m]
9466            .iter_mut()
9467            .zip(&v[(be - 1) * m..be * m])
9468        {
9469            *slot = x.widen();
9470        }
9471        for i in (0..last).rev() {
9472            let (o, p) = (i * m, (i + 1) * m);
9473            for j in 0..m {
9474                let (r, f) = step(v[(bs + i) * m + j].widen(), suf[p + j]);
9475                suf[o + j] = r;
9476                over |= f;
9477            }
9478        }
9479        std::mem::swap(&mut prev, &mut suf);
9480    }
9481    (!over).then_some(out)
9482}
9483
9484/// [`window_fold`] for one element per item — a plain time series, and the
9485/// shape worth writing the loops out for: each of the three runs over a
9486/// block is a walk over one slice, so the accumulator stays in a register
9487/// and nothing is bounds-checked per element.
9488///
9489/// A range of the output depends only on the blocks its own windows lie in,
9490/// so the output splits across threads with nothing shared: a chunk starting
9491/// at `lo` starts at the block holding item `lo`, and the first window it
9492/// writes begins in that same block.
9493fn window_fold_flat<S, T, F>(v: &[S], n: usize, w: usize, step: F) -> Option<Vec<T>>
9494where
9495    S: Widen<T>,
9496    T: Copy + Default + Send + Sync,
9497    F: Fn(T, T) -> (T, bool) + Sync + Send,
9498{
9499    let (out, ok) = par::fill(n - w + 1, |lo, part: &mut [T]| {
9500        window_fold_range(v, n, w, lo, part, &step)
9501    });
9502    ok.then_some(out)
9503}
9504
9505#[inline(always)]
9506fn window_fold_range_body<S, T, F>(
9507    v: &[S],
9508    n: usize,
9509    w: usize,
9510    lo: usize,
9511    out: &mut [T],
9512    step: &F,
9513) -> bool
9514where
9515    S: Widen<T>,
9516    T: Copy + Default,
9517    F: Fn(T, T) -> (T, bool),
9518{
9519    if out.is_empty() {
9520        return true;
9521    }
9522    let hi = lo + out.len();
9523    let mut pre = vec![T::default(); w];
9524    let mut suf = vec![T::default(); w];
9525    let mut prev = vec![T::default(); w];
9526    let mut over = false;
9527    let mut bs = lo / w * w;
9528    // The last item any window of this chunk needs is `hi + w - 2`.
9529    while bs < n && bs <= hi + w - 2 {
9530        let block = &v[bs..(bs + w).min(n)];
9531        let lb = block.len();
9532        let mut acc = block[0].widen();
9533        pre[0] = acc;
9534        for (slot, &x) in pre[1..lb].iter_mut().zip(&block[1..]) {
9535            let (r, o) = step(acc, x.widen());
9536            acc = r;
9537            over |= o;
9538            *slot = acc;
9539        }
9540        // Every window of this chunk whose last item is in this block. Its
9541        // first item is this block's start, or is in the block before —
9542        // which is never the case in the first block a chunk touches, since
9543        // that block holds item `lo` and no window here starts earlier.
9544        for e in bs.max(lo + w - 1)..(bs + lb).min(hi + w - 1) {
9545            let i = e + 1 - w;
9546            out[i - lo] = if i == bs {
9547                pre[e - bs]
9548            } else {
9549                let (r, o) = step(prev[i + w - bs], pre[e - bs]);
9550                over |= o;
9551                r
9552            };
9553        }
9554        let mut acc = block[lb - 1].widen();
9555        suf[lb - 1] = acc;
9556        for (slot, &x) in suf[..lb - 1].iter_mut().zip(&block[..lb - 1]).rev() {
9557            let (r, o) = step(x.widen(), acc);
9558            acc = r;
9559            over |= o;
9560            *slot = acc;
9561        }
9562        std::mem::swap(&mut prev, &mut suf);
9563        bs += w;
9564    }
9565    !over
9566}
9567
9568multiversioned! {
9569    /// The windows `lo .. lo + out.len()`. False when a step left the type.
9570    /// Compiled per CPU feature level; the prefix and suffix passes it runs
9571    /// are dependent chains, so what a wider vector reaches here is the
9572    /// pairing of the two, not the passes themselves.
9573    fn window_fold_range[S: Widen<T>, T: Copy + Default, F: Fn(T, T) -> (T, bool)](
9574        v: &[S],
9575        n: usize,
9576        w: usize,
9577        lo: usize,
9578        out: &mut [T],
9579        step: &F,
9580    ) -> bool = window_fold_range_body;
9581}
9582
9583/// The windows of `w` items of `v` that begin at `lo` and after, folded into
9584/// `out` — one item per window, `out.len()` of them.
9585///
9586/// The fused kernel folds the windows of a block it computed itself, and
9587/// calls this to do it: the blocking is counted from `v`'s own start, so a
9588/// caller whose buffer starts on a multiple of `w` groups every window
9589/// exactly as the pass over the whole argument groups it. False when a step
9590/// left the element type.
9591pub(crate) fn windows_into<S, T, F>(v: &[S], w: usize, lo: usize, out: &mut [T], step: &F) -> bool
9592where
9593    S: Widen<T>,
9594    T: Copy + Default,
9595    F: Fn(T, T) -> (T, bool),
9596{
9597    window_fold_range(v, v.len(), w, lo, out, step)
9598}
9599
9600fn window_i64<S: Widen<i64>>(
9601    op: ScalarDyad,
9602    v: &[S],
9603    n: usize,
9604    m: usize,
9605    w: usize,
9606) -> Option<Vec<i64>> {
9607    use ScalarDyad::*;
9608    match op {
9609        Add => window_fold(v, n, m, w, i64::overflowing_add),
9610        Mul => window_fold(v, n, m, w, i64::overflowing_mul),
9611        Min => window_fold(v, n, m, w, |a: i64, b: i64| (a.min(b), false)),
9612        Max => window_fold(v, n, m, w, |a: i64, b: i64| (a.max(b), false)),
9613        _ => None,
9614    }
9615}
9616
9617fn window_cx(op: ScalarDyad, v: &[Cx], n: usize, m: usize, w: usize) -> Option<Vec<Cx>> {
9618    use ScalarDyad::*;
9619    match op {
9620        Add => window_fold(v, n, m, w, |a: Cx, b: Cx| (cx::add(a, b), false)),
9621        Mul => window_fold(v, n, m, w, |a: Cx, b: Cx| (cx::mul(a, b), false)),
9622        _ => None,
9623    }
9624}
9625
9626fn window_f64<S: Widen<f64>>(
9627    op: ScalarDyad,
9628    v: &[S],
9629    n: usize,
9630    m: usize,
9631    w: usize,
9632) -> Option<Vec<f64>> {
9633    use ScalarDyad::*;
9634    match op {
9635        Add => window_fold(v, n, m, w, |a: f64, b: f64| block_f64(a + b)),
9636        Mul => window_fold(v, n, m, w, |a: f64, b: f64| block_f64(a * b)),
9637        Min => window_fold(v, n, m, w, |a: f64, b: f64| (a.min(b), false)),
9638        Max => window_fold(v, n, m, w, |a: f64, b: f64| (a.max(b), false)),
9639        _ => None,
9640    }
9641}
9642
9643/// Moving windows over a numeric buffer in two passes. None means this path
9644/// does not apply: only the associative arithmetic can be regrouped into
9645/// blocks, so subtraction and every non-scalar verb go the general way.
9646fn window_typed(op: ScalarDyad, d: &Data, n: usize, m: usize, w: usize) -> Option<Data> {
9647    use ScalarDyad::*;
9648    if !matches!(op, Add | Mul | Min | Max) {
9649        return None;
9650    }
9651    // As in the scan: integers and booleans window as integers, each read in
9652    // its own type, and the float retry rereads the same buffer.
9653    fn ints<S: Widen<i64> + Widen<f64>>(
9654        op: ScalarDyad,
9655        v: &[S],
9656        n: usize,
9657        m: usize,
9658        w: usize,
9659    ) -> Data {
9660        match window_i64(op, v, n, m, w) {
9661            Some(out) => Data::I64(out.into()),
9662            None => {
9663                Data::F64(window_f64(op, v, n, m, w).expect("the float fold cannot overflow").into())
9664            }
9665        }
9666    }
9667    match d {
9668        Data::F64(v) => Some(Data::F64(window_f64(op, v.as_slice(), n, m, w)?.into())),
9669        Data::Complex(v) => Some(Data::Complex(window_cx(op, v, n, m, w)?.into())),
9670        Data::I64(v) => Some(ints(op, v.as_slice(), n, m, w)),
9671        Data::Bool(v) => Some(ints(op, v.as_slice(), n, m, w)),
9672        // A bignum has no blockwise form: the exact types fold, scan and
9673        // window through the general path, one step at a time.
9674        Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
9675    }
9676}
9677
9678/// `u\ y` and `u\. y`: the verb applied to every prefix, or to every suffix.
9679fn runs(u: &Verb, y: &Array, back: bool, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
9680    let promoted = as_items(y);
9681    let base = promoted.as_ref().unwrap_or(y);
9682    let n = base.items();
9683    let m = base.item_size();
9684    // No items, so no runs: the answer's shape cannot come from the cells.
9685    // APL's scan keeps the shape it was given, whatever the function is.
9686    // J's takes the shape of the verb applied to the one run an empty
9687    // argument has, which is the argument itself: `,/\ i.0 3` is a 0 by 0
9688    // table where `+/\ i.0 3` is 0 by 3.
9689    if n == 0 {
9690        if ctx.cfg.rules.lang == crate::Lang::Apl {
9691            return Ok(Array::new(base.shape.clone(), Data::empty(base.dtype())));
9692        }
9693        let cell = u.is_pure().then(|| base.clone());
9694        return Ok(empty_frame(&[0], base.dtype(), cell, ctx, |cell, c| u.monad(cell, c, span)));
9695    }
9696    if n > 0 && base.dtype().is_numeric() && let Some(op) = folded_op(u) {
9697        // Folding from the right is the insert's own order, so it holds
9698        // for any step; folding from the left needs associativity.
9699        if (back || is_associative(op))
9700            && let Some(d) = scan_typed(op, base.row_major_data(), n, m, back)
9701        {
9702            return Ok(Array::new(base.shape.clone(), d));
9703        }
9704    }
9705    if n > 0 && let Verb::Reduce(inner) = u {
9706        if base.dtype().is_numeric()
9707            && let Some(c) = affine_step(inner)
9708            && let Some(d) = affine_scan(c, base, back)
9709        {
9710            return Ok(Array::new(base.shape.clone(), d));
9711        }
9712        // Suffix k is item k folded with suffix k+1, because right to left
9713        // is the insert's own order: one step per item, whatever the verb.
9714        // Prefixes have no such relation — prefix k and prefix k+1 share
9715        // their tail, not their head — so only this direction is a running
9716        // fold in general, and it is the direction `|. u/\. |. y` reverses
9717        // twice to reach.
9718        if back && u.is_pure() {
9719            let mut acc = base.item(n - 1);
9720            let mut cells = Vec::with_capacity(n);
9721            cells.push(acc.clone());
9722            for i in (0..n - 1).rev() {
9723                acc = inner.dyad(&base.item(i), &acc, ctx, span)?;
9724                cells.push(acc.clone());
9725            }
9726            cells.reverse();
9727            return assemble(&[n], cells, span);
9728        }
9729    }
9730    let apl = ctx.cfg.rules.lang == crate::Lang::Apl;
9731    let cells = each_cell(n, n * m, u.is_pure(), ctx, |i, c| {
9732        let part = if back { section(base, i, n) } else { section(base, 0, i + 1) };
9733        u.monad(&part, c, span)
9734    })?;
9735    if apl { assemble_items(&[n], cells, span) } else { assemble(&[n], cells, span) }
9736}
9737
9738/// The result of a window longer than the argument holds no items, but it
9739/// still has the shape of one: J learns that shape by running the verb on a
9740/// window of fills, and so does this. A verb that fails on fills, or a
9741/// window too large to build, leaves the result a plain empty vector.
9742fn empty_windows(u: &Verb, y: &Array, w: usize, ctx: &mut Ctx<'_>, span: Span) -> Array {
9743    let m = y.item_size();
9744    if u.is_pure() && let Some(cells) = w.checked_mul(m).filter(|&s| s <= 1 << 20) {
9745        let mut shape = y.shape.clone();
9746        shape[0] = w;
9747        let probe = Array::new(shape, fill_data(y.dtype(), cells));
9748        if let Ok(cell) = u.monad(&probe, ctx, span) {
9749            let mut shape = vec![0usize];
9750            shape.extend_from_slice(&cell.shape);
9751            return Array::new(shape, Data::empty(cell.dtype()));
9752        }
9753    }
9754    Array::new(vec![0], Data::empty(DType::I64))
9755}
9756
9757/// The window size: one integer atom.
9758fn window_size(x: &Array, near: NearInt, span: Span) -> Result<i64> {
9759    let v = x
9760        .to_i64_vec_near(near)
9761        .ok_or_else(|| Error::domain("the window size must be an integer", span))?;
9762    match v.as_slice() {
9763        [k] => Ok(*k),
9764        _ => Err(Error::new(
9765            ErrorKind::Length,
9766            "the window size must be a single number",
9767            Some(span),
9768        )),
9769    }
9770}
9771
9772/// `x u\ y`: the verb applied to runs of x items.
9773///
9774/// A positive x takes the overlapping windows of that length, of which there
9775/// are none when the argument is shorter; a negative one takes the
9776/// non-overlapping chunks of |x| items, the last of them short; and zero
9777/// takes the n+1 empty runs between and around the items, which is what J
9778/// does with it.
9779fn infix(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
9780    let k = window_size(x, ctx.cfg.near(), span)?;
9781    let promoted = as_items(y);
9782    let base = promoted.as_ref().unwrap_or(y);
9783    let n = base.items();
9784    let m = base.item_size();
9785    if k < 0 {
9786        let w = k.unsigned_abs() as usize;
9787        let count = n.div_ceil(w);
9788        let cells = each_cell(count, n * m, u.is_pure(), ctx, |i, c| {
9789            u.monad(&section(base, i * w, ((i + 1) * w).min(n)), c, span)
9790        })?;
9791        return assemble(&[count], cells, span);
9792    }
9793    let w = k as usize;
9794    if n < w {
9795        return Ok(empty_windows(u, base, w, ctx, span));
9796    }
9797    let count = n - w + 1;
9798    if w > 0 && base.dtype().is_numeric()
9799        && let Some(op) = folded_op(u) && let Some(d) = window_typed(op, &base.data, n, m, w)
9800    {
9801        let mut shape = base.shape.clone();
9802        shape[0] = count;
9803        return Ok(Array::new(shape, d));
9804    }
9805    let work = count.saturating_mul(w).saturating_mul(m);
9806    let cells = each_cell(count, work, u.is_pure(), ctx, |i, c| {
9807        u.monad(&section(base, i, i + w), c, span)
9808    })?;
9809    assemble(&[count], cells, span)
9810}
9811
9812/// `n f/ y` (APL): the reduce of every window of n items along the leading
9813/// axis. `f/` itself decides what folding a window means, so the operand's
9814/// own rules — the identity of an empty fold, the enclosure APL's insert
9815/// puts round a non-scalar value — carry over unchanged.
9816///
9817/// n is one integer. A positive one takes the overlapping windows in order;
9818/// a negative one takes the same windows with their items REVERSED, which
9819/// only shows on a fold that is not commutative (`¯2-/1 2 3` is `1 1` where
9820/// `2-/1 2 3` is `¯1 ¯1`); zero takes the `1+≢y` empty windows, so the
9821/// answer is that many copies of the operand's identity. The axis loses
9822/// `|n|-1` items, so `|n|` may reach `1+≢y` — one item further and there is
9823/// no such window, which is an error rather than a shorter answer.
9824fn nwise(f: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
9825    // How many numbers there are is settled before what they are: a left
9826    // argument of two is a length error whatever it holds, which is what
9827    // keeps `1 1+/2 3` from reading as a compress.
9828    if x.count() != 1 {
9829        return Err(Error::new(
9830            ErrorKind::Length,
9831            "the window size must be a single number",
9832            Some(span),
9833        ));
9834    }
9835    let k = window_size(x, ctx.cfg.near(), span)?;
9836    let promoted = as_items(y);
9837    // A rank-0 argument has no axis to window. One item is what `≢` counts
9838    // it as, and a window of one leaves it exactly as it was, rank included;
9839    // any other window has to make the axis the argument never had.
9840    if promoted.is_some() && k.unsigned_abs() == 1 {
9841        return Ok(y.clone());
9842    }
9843    let base = promoted.as_ref().unwrap_or(y).to_row_major();
9844    let base = &base;
9845    let n = base.items();
9846    let m = base.item_size();
9847    let w = k.unsigned_abs() as usize;
9848    if w > n + 1 {
9849        return Err(Error::domain(
9850            format!("a window of {w} does not fit an axis of {n}"),
9851            span,
9852        ));
9853    }
9854    let count = n + 1 - w;
9855    let fold = Verb::Reduce(Box::new(f.clone()));
9856    if count == 0 {
9857        return Ok(empty_windows(&fold, base, w, ctx, span));
9858    }
9859    // The blockwise fold the infix already has. It runs over whole items at
9860    // full rank, which is what folding the elements along the axis comes to
9861    // for the arithmetic operands it covers, and those are all commutative,
9862    // so a reversed window folds to the same value.
9863    if w > 0
9864        && base.dtype().is_numeric()
9865        && let Some(op) = scalar_dyad_of(f)
9866        && let Some(d) = window_typed(op, base.row_major_data(), n, m, w)
9867    {
9868        let mut shape = base.shape.clone();
9869        shape[0] = count;
9870        return Ok(Array::new(shape, d));
9871    }
9872    let work = count.saturating_mul(w.max(1)).saturating_mul(m);
9873    let cells = each_cell(count, work, f.is_pure(), ctx, |i, c| {
9874        let win = section(base, i, i + w);
9875        let win = if k < 0 { reverse(&win) } else { win };
9876        fold.monad(&win, c, span)
9877    })?;
9878    assemble(&[count], cells, span)
9879}
9880
9881/// `u^:n y` and `x u^:n y`: n applications of the verb, or iteration until
9882/// the result stops changing.
9883fn power(
9884    u: &Verb,
9885    p: Power,
9886    x: Option<&Array>,
9887    y: &Array,
9888    ctx: &mut Ctx<'_>,
9889    span: Span,
9890) -> Result<Array> {
9891    let step = |acc: &Array, c: &mut Ctx<'_>| match x {
9892        Some(x) => u.dyad(x, acc, c, span),
9893        None => u.monad(acc, c, span),
9894    };
9895    match p {
9896        Power::Times(n) => {
9897            let mut acc = y.clone();
9898            for _ in 0..n {
9899                acc = step(&acc, ctx)?;
9900            }
9901            Ok(acc)
9902        }
9903        Power::Converge => {
9904            let mut acc = y.clone();
9905            for _ in 0..CONVERGE_LIMIT {
9906                let next = step(&acc, ctx)?;
9907                if arrays_match(&next, &acc, ctx.cfg.tol) {
9908                    return Ok(next);
9909                }
9910                acc = next;
9911            }
9912            Err(Error::domain("the iteration did not converge", span))
9913        }
9914        // One answer per count. The counts are taken in the order given and
9915        // the walk is shared: the applications are counted from 0 upwards
9916        // and an answer is kept wherever a count asks for it.
9917        Power::Each(ref counts) => {
9918            let mut acc = y.clone();
9919            let mut done = 0u64;
9920            let mut order: Vec<usize> = (0..counts.len()).collect();
9921            order.sort_by_key(|&i| counts[i]);
9922            let mut cells: Vec<Option<Array>> = vec![None; counts.len()];
9923            for i in order {
9924                while done < counts[i] {
9925                    acc = step(&acc, ctx)?;
9926                    done += 1;
9927                }
9928                cells[i] = Some(acc.clone());
9929            }
9930            let cells: Vec<Array> = cells.into_iter().map(|c| c.expect("every count filled")).collect();
9931            assemble(&[cells.len()], cells, span)
9932        }
9933        Power::ConvergeTrace => {
9934            let mut acc = y.clone();
9935            let mut cells = vec![acc.clone()];
9936            for _ in 0..CONVERGE_LIMIT {
9937                let next = step(&acc, ctx)?;
9938                if arrays_match(&next, &acc, ctx.cfg.tol) {
9939                    return assemble(&[cells.len()], cells, span);
9940                }
9941                cells.push(next.clone());
9942                acc = next;
9943            }
9944            Err(Error::domain("the iteration did not converge", span))
9945        }
9946    }
9947}
9948
9949/// `u^:v y` and `x u^:v y` (J): the verb `v` says how many times to apply
9950/// `u`. `(u^:v)^:_` is the while loop the idiom is written with.
9951fn power_v(
9952    u: &Verb,
9953    v: &Verb,
9954    x: Option<&Array>,
9955    y: &Array,
9956    ctx: &mut Ctx<'_>,
9957    span: Span,
9958) -> Result<Array> {
9959    let count = match x {
9960        Some(x) => v.dyad(x, y, ctx, span)?,
9961        None => v.monad(y, ctx, span)?,
9962    };
9963    let n = count
9964        .to_i64_vec_near(ctx.cfg.near())
9965        .ok_or_else(|| Error::domain("the power count must be an integer", span))?;
9966    if n.len() != 1 {
9967        return Err(Error::not_yet("a list of power counts (u^:v with several)", span));
9968    }
9969    let n = n[0];
9970    if n < 0 {
9971        return Err(Error::not_yet("a negative power (the verb's inverse)", span));
9972    }
9973    power(u, Power::Times(n as u64), x, y, ctx, span)
9974}
9975
9976/// `f⍣g y` (APL): apply `f` until `new g old` holds.
9977fn power_until(
9978    u: &Verb,
9979    test: &Verb,
9980    y: &Array,
9981    ctx: &mut Ctx<'_>,
9982    span: Span,
9983) -> Result<Array> {
9984    let mut acc = y.clone();
9985    for _ in 0..CONVERGE_LIMIT {
9986        let next = u.monad(&acc, ctx, span)?;
9987        let done = test.dyad(&next, &acc, ctx, span)?;
9988        let stop = done
9989            .to_f64_vec()
9990            .ok_or_else(|| Error::domain("the ⍣ test must answer with numbers", span))?;
9991        if !stop.is_empty() && stop.iter().all(|&v| v != 0.0) {
9992            return Ok(next);
9993        }
9994        acc = next;
9995    }
9996    Err(Error::domain("the iteration did not converge", span))
9997}
9998
9999/// `f[k]` (APL): `f` applied along axis `k`.
10000///
10001/// The axis is brought to the front, the verb runs on the leading axis, and
10002/// a result that kept the argument's rank has the axis put back — which is
10003/// what separates a reduction (rank drops, axes stay in order) from a scan
10004/// or a reversal (rank kept).
10005fn along_axis(
10006    u: &Verb,
10007    x: Option<&Array>,
10008    y: &Array,
10009    k: usize,
10010    ctx: &mut Ctx<'_>,
10011    span: Span,
10012) -> Result<Array> {
10013    if k >= y.rank().max(1) {
10014        return Err(Error::new(
10015            ErrorKind::Rank,
10016            format!("axis {k} does not exist on an argument of rank {}", y.rank()),
10017            Some(span),
10018        ));
10019    }
10020    let moved = axis_to_front(y, k);
10021    let r = moved.rank();
10022    let out = match x {
10023        Some(x) => u.dyad(x, &moved, ctx, span)?,
10024        None => u.monad(&moved, ctx, span)?,
10025    };
10026    if out.rank() == r {
10027        return Ok(front_to_axis(&out, k));
10028    }
10029    Ok(out)
10030}
10031
10032// ------------------------------------------------- wave 3: search and steps
10033
10034/// `I. y` (J) / `⍸ y` (APL): index `i` repeated `y[i]` times.
10035///
10036/// J applies at rank 1, so a higher-rank argument frames the vector answers;
10037/// APL applies to the whole argument and answers a rank-2-or-higher one with
10038/// one boxed coordinate vector per occurrence.
10039fn where_indices(y: &Array, origin: i64, boxed: bool, near: NearInt, span: Span) -> Result<Array> {
10040    let counts = y
10041        .to_i64_vec_near(near)
10042        .ok_or_else(|| Error::domain("indices needs non-negative integers", span))?;
10043    if counts.iter().any(|&c| c < 0) {
10044        return Err(Error::domain("indices needs non-negative integers", span));
10045    }
10046    if !boxed || y.rank() < 2 {
10047        let mut out = Vec::new();
10048        for (i, &c) in counts.iter().enumerate() {
10049            for _ in 0..c {
10050                out.push(origin + i as i64);
10051            }
10052        }
10053        return Ok(Array::from_i64(out));
10054    }
10055    let r = y.rank();
10056    let mut coord = vec![0usize; r];
10057    let mut out: Vec<Array> = Vec::new();
10058    for &c in &counts {
10059        if c > 0 {
10060            let point =
10061                Array::from_i64(coord.iter().map(|&k| origin + k as i64).collect::<Vec<_>>());
10062            for _ in 0..c {
10063                out.push(point.clone());
10064            }
10065        }
10066        odometer(&mut coord, &y.shape);
10067    }
10068    Ok(Array::new(vec![out.len()], Data::Box(out.into())))
10069}
10070
10071/// `I.^:_1 y`: how many times each index from zero to the largest occurs in
10072/// y, which is the counting vector `I.` was given. An empty argument counts
10073/// nothing.
10074fn indices_inverse(y: &Array, near: NearInt, span: Span) -> Result<Array> {
10075    if y.count() == 0 {
10076        return Ok(Array::empty(DType::I64));
10077    }
10078    let at = y
10079        .to_i64_vec_near(near)
10080        .ok_or_else(|| Error::domain("the obverse of indices needs integers", span))?;
10081    if at.iter().any(|&i| i < 0) {
10082        return Err(Error::domain("the obverse of indices needs non-negative integers", span));
10083    }
10084    let Some(&top) = at.iter().max() else {
10085        return Ok(Array::empty(DType::I64));
10086    };
10087    let mut counts = vec![0i64; top as usize + 1];
10088    for &i in &at {
10089        counts[i as usize] += 1;
10090    }
10091    Ok(Array::from_i64(counts))
10092}
10093
10094/// `x I. y` / `x ⍸ y`: which interval of the ascending `x` each cell of `y`
10095/// falls in — the number of items of `x` strictly below it.
10096///
10097/// `offset` is what the language adds to that count: nothing in J, and
10098/// `⎕IO - 1` in APL, which is what both references answer.
10099fn interval_index(
10100    x: &Array,
10101    y: &Array,
10102    offset: i64,
10103    closed: bool,
10104    tol: Tol,
10105    ord: Grading,
10106    span: Span,
10107) -> Result<Array> {
10108    // Characters, symbols and boxes have an order of their own, and no
10109    // tolerance: the bounds are searched by that order instead of by value.
10110    if !x.dtype().is_numeric() || !y.dtype().is_numeric() {
10111        return ordered_interval_index(x, y, offset, closed, ord, span);
10112    }
10113    let bounds = x
10114        .to_f64_vec()
10115        .ok_or_else(|| Error::domain("interval index needs numeric bounds", span))?;
10116    let vals = y
10117        .to_f64_vec()
10118        .ok_or_else(|| Error::domain("interval index needs numeric values", span))?;
10119    let out: Vec<i64> = vals
10120        .iter()
10121        .map(|&v| {
10122            // APL counts a bound EQUAL to the value, J does not: `1 3 5⍸3`
10123            // is 2 where `1 3 5 I. 3` is 1.
10124            let count =
10125                bounds.iter().filter(|&&b| if closed { !tol.lt(v, b) } else { tol.lt(b, v) });
10126            offset + count.count() as i64
10127        })
10128        .collect();
10129    Ok(Array::new(y.shape.clone(), Data::I64(out.into())))
10130}
10131
10132/// [`interval_index`] over the element types that are ordered but not
10133/// numeric. Both sides must be the same type — a character bound has
10134/// nothing to say about where a symbol falls.
10135fn ordered_interval_index(
10136    x: &Array,
10137    y: &Array,
10138    offset: i64,
10139    closed: bool,
10140    ord: Grading,
10141    span: Span,
10142) -> Result<Array> {
10143    let (xr, yr) = (x.to_row_major(), y.to_row_major());
10144    let (bounds, vals) = (&xr.data, &yr.data);
10145    let cmp = |i: usize, j: usize| -> Option<std::cmp::Ordering> {
10146        match (bounds, vals) {
10147            (Data::Char(p), Data::Char(q)) => Some(p[i].cmp(&q[j])),
10148            (Data::Symbol(p), Data::Symbol(q)) => Some(crate::symbol::cmp(p[i], q[j])),
10149            // J orders boxed values against each other by the same total
10150            // order `/:` grades them with, so `I.` can search among them.
10151            // APL2 gives its nested values no such order, and GNU APL's own
10152            // is an extension libjay does not follow: see divergences.txt.
10153            (Data::Box(p), Data::Box(q)) if ord.tao == Tao::J => {
10154                Some(cmp_items_total(&p[i], &q[j], ord))
10155            }
10156            _ => None,
10157        }
10158    };
10159    let mut out = Vec::with_capacity(y.count());
10160    for j in 0..y.count() {
10161        let mut count = 0i64;
10162        for i in 0..x.count() {
10163            let ord = cmp(i, j).ok_or_else(|| {
10164                Error::domain(
10165                    format!(
10166                        "interval index compares {} bounds with {} values",
10167                        x.dtype().name(),
10168                        y.dtype().name()
10169                    ),
10170                    span,
10171                )
10172            })?;
10173            // APL counts a bound EQUAL to the value, J does not.
10174            count += i64::from(if closed { ord.is_le() } else { ord.is_lt() });
10175        }
10176        out.push(offset + count);
10177    }
10178    Ok(Array::new(y.shape.clone(), Data::I64(out.into())))
10179}
10180
10181/// `i: y` (J): the integers from `-y` to `y`, one step apart. The count is
10182/// `1 + <. 2 * | y`, and a negative argument counts down.
10183fn steps(y: &Array, span: Span) -> Result<Array> {
10184    let vals = y.to_f64_vec().ok_or_else(|| Error::domain("steps needs a number", span))?;
10185    let v = match vals.first() {
10186        Some(&v) if v.is_finite() => v,
10187        _ => return Err(Error::domain("steps needs a finite number", span)),
10188    };
10189    let n = (2.0 * v.abs()).floor();
10190    if n > 1e7 {
10191        return Err(Error::domain("steps would produce too many items", span));
10192    }
10193    let n = n as i64 + 1;
10194    let step = if v < 0.0 { -1.0 } else { 1.0 };
10195    let start = -v;
10196    if v.fract() == 0.0 {
10197        let start = start as i64;
10198        let step = step as i64;
10199        return Ok(Array::from_i64((0..n).map(|k| start + k * step).collect()));
10200    }
10201    Ok(Array::from_f64((0..n).map(|k| start + k as f64 * step).collect()))
10202}
10203
10204/// `x i: y`: where each cell of `y` LAST sits among the items of `x`.
10205fn index_of_last(x: &Array, y: &Array, origin: i64, tol: Tol) -> Array {
10206    let cell_rank = x.rank().saturating_sub(1).min(y.rank());
10207    let frame_rank = y.rank() - cell_rank;
10208    let frame: Vec<usize> = y.shape[..frame_rank].to_vec();
10209    let nf: usize = frame.iter().product();
10210    let items = x.items();
10211    let mut out = Vec::with_capacity(nf);
10212    for i in 0..nf {
10213        let cell = y.cell_at(frame_rank, i);
10214        let at = (0..items)
10215            .rev()
10216            .find(|&j| arrays_match(&cell, &item_or_self(x, j), tol))
10217            .unwrap_or(items);
10218        out.push(origin + at as i64);
10219    }
10220    Array::new(frame, Data::I64(out.into()))
10221}
10222
10223// ----------------------------------------------------------- roll and deal
10224
10225/// `? y` / `?. y`: every element of y replaced by a random value below it.
10226///
10227/// The whole argument is one draw, taken in ravel order, which is what
10228/// makes `?. 5 # 100` five different numbers rather than one repeated.
10229fn roll(
10230    y: &Array,
10231    origin: i64,
10232    fixed: bool,
10233    float_at_zero: bool,
10234    near: NearInt,
10235    span: Span,
10236) -> Result<Array> {
10237    let bounds = y
10238        .to_i64_vec_near(near)
10239        .ok_or_else(|| Error::domain("roll needs whole numbers", span))?;
10240    if bounds.iter().any(|&b| b < 0) {
10241        return Err(Error::domain("roll needs non-negative numbers", span));
10242    }
10243    if !float_at_zero && bounds.contains(&0) {
10244        return Err(Error::domain("? 0 has no value: the range is empty", span));
10245    }
10246    // A zero anywhere makes the whole answer float, as J's does.
10247    let any_zero = bounds.contains(&0);
10248    crate::rng::with(fixed, |g| {
10249        if any_zero {
10250            let out: Vec<f64> = bounds
10251                .iter()
10252                .map(|&b| {
10253                    if b == 0 {
10254                        g.unit()
10255                    } else {
10256                        (origin + g.below(b as u64) as i64) as f64
10257                    }
10258                })
10259                .collect();
10260            return Ok(Array::new(y.shape.clone(), Data::F64(out.into())));
10261        }
10262        let out: Vec<i64> =
10263            bounds.iter().map(|&b| origin + g.below(b as u64) as i64).collect();
10264        Ok(Array::new(y.shape.clone(), Data::I64(out.into())))
10265    })
10266}
10267
10268/// `x ? y` / `x ?. y`: x distinct values drawn from the y below `origin+y`.
10269fn deal(
10270    x: &Array,
10271    y: &Array,
10272    origin: i64,
10273    fixed: bool,
10274    near: NearInt,
10275    span: Span,
10276) -> Result<Array> {
10277    let want = one_whole(x, "the count dealt", near, span)?;
10278    let from = one_whole(y, "the range dealt from", near, span)?;
10279    if want < 0 || from < 0 {
10280        return Err(Error::domain("deal needs non-negative numbers", span));
10281    }
10282    if want > from {
10283        return Err(Error::domain(
10284            format!("cannot deal {want} distinct value(s) from {from}"),
10285            span,
10286        ));
10287    }
10288    if want == 0 {
10289        return Ok(Array::from_i64(Vec::new()));
10290    }
10291    let drawn = crate::rng::with(fixed, |g| g.deal(want as usize, from as u64));
10292    Ok(Array::from_i64(drawn.into_iter().map(|v| v + origin).collect()))
10293}
10294
10295/// One whole number from a one-element argument.
10296fn one_whole(a: &Array, what: &str, near: NearInt, span: Span) -> Result<i64> {
10297    let v = a
10298        .to_i64_vec_near(near)
10299        .ok_or_else(|| Error::domain(format!("{what} must be a whole number"), span))?;
10300    match v[..] {
10301        [n] => Ok(n),
10302        _ => Err(Error::new(
10303            ErrorKind::Rank,
10304            format!("{what} must be one number"),
10305            Some(span),
10306        )),
10307    }
10308}
10309
10310// ------------------------------------------------------------------ primes
10311
10312/// The `n`-th prime, counting from zero (`p: n`).
10313fn nth_prime(n: i64, span: Span) -> Result<i64> {
10314    if n < 0 {
10315        return Err(Error::domain("the prime index must not be negative", span));
10316    }
10317    const LIMIT: i64 = 5_000_000;
10318    if n >= LIMIT {
10319        return Err(Error::domain(
10320            format!("prime index {n} is beyond the {LIMIT}th prime"),
10321            span,
10322        ));
10323    }
10324    // An upper bound for p_n (n counted from zero): n < 6 is tabulated,
10325    // above that Rosser's bound n(ln n + ln ln n) holds.
10326    let k = (n + 1) as f64;
10327    let bound = if n < 6 { 15.0 } else { k * (k.ln() + k.ln().ln()) };
10328    let bound = bound.ceil() as usize + 1;
10329    let mut sieve = vec![true; bound + 1];
10330    sieve[0] = false;
10331    if bound >= 1 {
10332        sieve[1] = false;
10333    }
10334    let mut p = 2usize;
10335    while p * p <= bound {
10336        if sieve[p] {
10337            let mut q = p * p;
10338            while q <= bound {
10339                sieve[q] = false;
10340                q += p;
10341            }
10342        }
10343        p += 1;
10344    }
10345    let mut seen = 0i64;
10346    for (v, &is_p) in sieve.iter().enumerate() {
10347        if is_p {
10348            if seen == n {
10349                return Ok(v as i64);
10350            }
10351            seen += 1;
10352        }
10353    }
10354    Err(Error::internal("the prime sieve was too small"))
10355}
10356
10357/// `q: n`: the prime factors of n, ascending, with multiplicity.
10358fn prime_factors(n: i64, span: Span) -> Result<Vec<i64>> {
10359    if n < 1 {
10360        return Err(Error::domain("prime factors need a positive integer", span));
10361    }
10362    let mut out = Vec::new();
10363    let mut m = n;
10364    let mut d = 2i64;
10365    while d.saturating_mul(d) <= m {
10366        while m % d == 0 {
10367            out.push(d);
10368            m /= d;
10369        }
10370        d += if d == 2 { 1 } else { 2 };
10371    }
10372    if m > 1 {
10373        out.push(m);
10374    }
10375    Ok(out)
10376}
10377
10378// --------------------------------------------------------- matrix division
10379
10380/// Least-squares solution of `a x = b` by Householder QR.
10381///
10382/// `a` is `m` by `n` in row-major order with `m >= n`, `b` is `m` by `k`.
10383/// The answer is `n` by `k`. None when `a` has not got full column rank,
10384/// which both references refuse.
10385fn lstsq(a: &[f64], m: usize, n: usize, b: &[f64], k: usize) -> Option<Vec<f64>> {
10386    // Work on copies: the factorisation overwrites both.
10387    let mut r = a.to_vec();
10388    let mut c = b.to_vec();
10389    let at = |i: usize, j: usize, w: usize| i * w + j;
10390    let scale = a.iter().fold(0.0f64, |acc, v| acc.max(v.abs()));
10391    if scale == 0.0 {
10392        return None;
10393    }
10394    for j in 0..n {
10395        // The Householder vector for column j below the diagonal.
10396        let norm = (j..m).map(|i| r[at(i, j, n)] * r[at(i, j, n)]).sum::<f64>().sqrt();
10397        if norm <= 1e-13 * scale {
10398            return None;
10399        }
10400        let alpha = if r[at(j, j, n)] > 0.0 { -norm } else { norm };
10401        let mut v = vec![0.0f64; m];
10402        for i in j..m {
10403            v[i] = r[at(i, j, n)];
10404        }
10405        v[j] -= alpha;
10406        let vnorm2: f64 = (j..m).map(|i| v[i] * v[i]).sum();
10407        if vnorm2 > 0.0 {
10408            for col in j..n {
10409                let dot: f64 = (j..m).map(|i| v[i] * r[at(i, col, n)]).sum();
10410                let f = 2.0 * dot / vnorm2;
10411                for i in j..m {
10412                    r[at(i, col, n)] -= f * v[i];
10413                }
10414            }
10415            for col in 0..k {
10416                let dot: f64 = (j..m).map(|i| v[i] * c[at(i, col, k)]).sum();
10417                let f = 2.0 * dot / vnorm2;
10418                for i in j..m {
10419                    c[at(i, col, k)] -= f * v[i];
10420                }
10421            }
10422        }
10423    }
10424    // Back-substitute the upper triangle.
10425    let mut x = vec![0.0f64; n * k];
10426    for col in 0..k {
10427        for i in (0..n).rev() {
10428            let mut acc = c[at(i, col, k)];
10429            for j in i + 1..n {
10430                acc -= r[at(i, j, n)] * x[at(j, col, k)];
10431            }
10432            let d = r[at(i, i, n)];
10433            if d.abs() <= 1e-13 * scale {
10434                return None;
10435            }
10436            x[at(i, col, k)] = acc / d;
10437        }
10438    }
10439    Some(x)
10440}
10441
10442/// A numeric argument as an `m` by `n` row-major buffer. Rank 0 is 1 by 1
10443/// and rank 1 is `m` by 1, which is how both references read them.
10444fn as_matrix(a: &Array, span: Span) -> Result<(Vec<f64>, usize, usize)> {
10445    let v = a
10446        .to_f64_vec()
10447        .ok_or_else(|| Error::domain("matrix division needs numeric data", span))?;
10448    match a.rank() {
10449        0 => Ok((v, 1, 1)),
10450        1 => {
10451            let m = a.shape[0];
10452            Ok((v, m, 1))
10453        }
10454        2 => Ok((v, a.shape[0], a.shape[1])),
10455        _ => Err(Error::new(
10456            ErrorKind::Rank,
10457            "matrix division needs an argument of rank 2 or less",
10458            Some(span),
10459        )),
10460    }
10461}
10462
10463/// `%. y` / `⌹ y`: the inverse of a square matrix, or the least-squares
10464/// pseudo-inverse of a taller one. A wider one is refused, as both
10465/// references refuse it.
10466fn matrix_inverse(y: &Array, span: Span) -> Result<Array> {
10467    let (a, m, n) = as_matrix(y, span)?;
10468    if m < n {
10469        return Err(Error::new(
10470            ErrorKind::Length,
10471            format!("cannot invert a {m} by {n} matrix: it has more columns than rows"),
10472            Some(span),
10473        ));
10474    }
10475    let mut eye = vec![0.0f64; m * m];
10476    for i in 0..m {
10477        eye[i * m + i] = 1.0;
10478    }
10479    let x = lstsq(&a, m, n, &eye, m)
10480        .ok_or_else(|| Error::domain("the matrix is singular", span))?;
10481    // A rank-2 argument gives the n by m pseudo-inverse; a vector or scalar
10482    // keeps its own shape, which is what J prints for them.
10483    let shape = if y.rank() == 2 { vec![n, m] } else { y.shape.clone() };
10484    Ok(Array::new(shape, Data::F64(x.into())))
10485}
10486
10487/// `x %. y` / `x ⌹ y`: the least-squares solution of `y a = x`.
10488fn matrix_divide(x: &Array, y: &Array, span: Span) -> Result<Array> {
10489    let (a, m, n) = as_matrix(y, span)?;
10490    let (b, bm, k) = as_matrix(x, span)?;
10491    if bm != m {
10492        return Err(Error::new(
10493            ErrorKind::Length,
10494            format!("the system has {m} rows but the right-hand side has {bm}"),
10495            Some(span),
10496        ));
10497    }
10498    if m < n {
10499        return Err(Error::new(
10500            ErrorKind::Length,
10501            format!("the {m} by {n} system is underdetermined"),
10502            Some(span),
10503        ));
10504    }
10505    let sol = lstsq(&a, m, n, &b, k)
10506        .ok_or_else(|| Error::domain("the system is singular", span))?;
10507    // The right-hand side's own rank decides the answer's: a vector in gives
10508    // one solution vector, a matrix in gives one column per column.
10509    let shape = if x.rank() == 2 { vec![n, k] } else { vec![n] };
10510    Ok(Array::new(shape, Data::F64(sol.into())))
10511}
10512
10513// ----------------------------------------------------- indexing and amend
10514
10515/// `x ⌷ y` (APL2): one scalar index per axis of y.
10516fn squad(x: &Array, y: &Array, origin: i64, leading: bool, near: NearInt, span: Span) -> Result<Array> {
10517    if x.rank() > 1 {
10518        return Err(Error::new(
10519            ErrorKind::Rank,
10520            "the index of ⌷ must be a scalar or a vector",
10521            Some(span),
10522        ));
10523    }
10524    // One item of x per axis of y — per LEADING axis where the dialect
10525    // reads it that way, so a shorter index leaves the trailing axes
10526    // whole. An item is a scalar, which drops its axis, or an enclosed
10527    // vector, which keeps it and selects that many.
10528    let items: Vec<Array> = if x.rank() == 0 { vec![x.clone()] } else { x.cells(1) };
10529    let named = items.len();
10530    if named > y.rank() || (!leading && named != y.rank()) {
10531        return Err(Error::new(
10532            ErrorKind::Rank,
10533            format!("{} index(es) for an argument of rank {}", named, y.rank()),
10534            Some(span),
10535        ));
10536    }
10537    let mut specs = Vec::with_capacity(items.len());
10538    let mut shape = Vec::new();
10539    for (k, item) in items.iter().enumerate() {
10540        let spec = match item.as_boxes() {
10541            Some(bs) if item.rank() == 0 => bs[0].clone(),
10542            _ => item.clone(),
10543        };
10544        let idx = spec
10545            .to_i64_vec_near(near)
10546            .ok_or_else(|| Error::domain("index must be an integer", span))?;
10547        for &i in &idx {
10548            let j = i - origin;
10549            if j < 0 || j as usize >= y.shape[k] {
10550                return Err(Error::domain(
10551                    format!("index {i} is out of range on axis {k}"),
10552                    span,
10553                ));
10554            }
10555        }
10556        shape.extend_from_slice(&spec.shape);
10557        specs.push((spec.shape.clone(), idx));
10558    }
10559    // An index shorter than the rank names the leading axes only; every
10560    // trailing axis comes through whole.
10561    for k in named..y.rank() {
10562        let n = y.shape[k];
10563        shape.push(n);
10564        specs.push((vec![n], (0..n as i64).map(|i| i + origin).collect()));
10565    }
10566    let y = y.to_row_major();
10567    let st = strides(&y.shape);
10568    let total: usize = shape.iter().product();
10569    let mut data = Data::empty(y.dtype());
10570    let mut coord = vec![0usize; shape.len()];
10571    for _ in 0..total {
10572        let mut at = 0usize;
10573        let mut used = 0usize;
10574        for (k, (sshape, idx)) in specs.iter().enumerate() {
10575            let sst = strides(sshape);
10576            let pick: usize = (0..sshape.len()).map(|a| coord[used + a] * sst[a]).sum();
10577            used += sshape.len();
10578            at += (idx[pick] - origin) as usize * st[k];
10579        }
10580        push_elem(&mut data, y.row_major_data(), at);
10581        odometer(&mut coord, &shape);
10582    }
10583    Ok(Array::new(shape, data))
10584}
10585
10586/// One bracket slot of APL indexing: axis `axis` of `y` selected by `x`.
10587///
10588/// A scalar index drops the axis, any other shape splices in. `rank`, when
10589/// it is not zero, is the number of slots the brackets held: the slot that
10590/// sees the whole array checks it, and the others have already been applied
10591/// to a smaller one.
10592fn select_axis(
10593    x: &Array,
10594    y: &Array,
10595    axis: usize,
10596    rank: usize,
10597    origin: i64,
10598    near: NearInt,
10599    span: Span,
10600) -> Result<Array> {
10601    if rank != 0 && y.rank() != rank {
10602        return Err(Error::new(
10603            ErrorKind::Rank,
10604            format!("{rank} index slot(s) for an argument of rank {}", y.rank()),
10605            Some(span),
10606        ));
10607    }
10608    if axis >= y.rank() {
10609        return Err(Error::new(
10610            ErrorKind::Rank,
10611            format!("axis {axis} does not exist on an argument of rank {}", y.rank()),
10612            Some(span),
10613        ));
10614    }
10615    let idx = x
10616        .to_i64_vec_near(near)
10617        .ok_or_else(|| Error::domain("index must be an integer", span))?;
10618    let len = y.shape[axis];
10619    let mut picks = Vec::with_capacity(idx.len());
10620    for &i in &idx {
10621        let j = i - origin;
10622        if j < 0 || j as usize >= len {
10623            return Err(Error::domain(
10624                format!("index {i} is out of range: axis {axis} has {len} items"),
10625                span,
10626            ));
10627        }
10628        picks.push(j as usize);
10629    }
10630    let mut shape = Vec::with_capacity(y.rank() + x.rank());
10631    shape.extend_from_slice(&y.shape[..axis]);
10632    shape.extend_from_slice(&x.shape);
10633    shape.extend_from_slice(&y.shape[axis + 1..]);
10634    let outer: usize = y.shape[..axis].iter().product();
10635    let inner: usize = y.shape[axis + 1..].iter().product();
10636    let mut data = Data::empty(y.dtype());
10637    for o in 0..outer {
10638        for &p in &picks {
10639            let base = (o * len + p) * inner;
10640            for e in 0..inner {
10641                push_elem(&mut data, &y.data, base + e);
10642            }
10643        }
10644    }
10645    Ok(Array::new(shape, data))
10646}
10647
10648/// `x m} y` (J): the items of `y` at the indices `m`, replaced by `x`.
10649///
10650/// `x` is either one item, used at every index, or one item per index.
10651fn amend(m: &Array, x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
10652    if y.rank() == 0 {
10653        return Err(Error::new(ErrorKind::Rank, "cannot amend a scalar", Some(span)));
10654    }
10655    // A boxed m is J's index specification, the same one `{` reads.
10656    if let Some(spec) = m.as_boxes().and_then(<[Array]>::first) {
10657        let spec = index_spec(spec, y, near, span)?;
10658        return amend_spec(&spec, x, y, span);
10659    }
10660    let idx = m
10661        .to_i64_vec_near(near)
10662        .ok_or_else(|| Error::domain("amend indices must be integers", span))?;
10663    let items = y.items() as i64;
10664    let mut at = Vec::with_capacity(idx.len());
10665    for &i in &idx {
10666        let k = if i < 0 { i + items } else { i };
10667        if k < 0 || k >= items {
10668            return Err(Error::domain(
10669                format!("index {i} is out of range: the argument has {items} items"),
10670                span,
10671            ));
10672        }
10673        at.push(k as usize);
10674    }
10675    let cell = y.item_size();
10676    let per_index = if x.count() == cell {
10677        false
10678    } else if x.count() == cell * at.len() {
10679        true
10680    } else {
10681        return Err(Error::new(
10682            ErrorKind::Length,
10683            format!(
10684                "cannot amend {} item(s) of {} element(s) each with {} element(s)",
10685                at.len(),
10686                cell,
10687                x.count()
10688            ),
10689            Some(span),
10690        ));
10691    };
10692    // The result holds both kinds of value, so it takes the wider type:
10693    // amending an integer list with 1.5 gives a float list, as J's does.
10694    let Some(t) = DType::promote(x.dtype(), y.dtype()) else {
10695        return Err(Error::new(
10696            ErrorKind::Type,
10697            "the replacement and the argument hold different kinds of value",
10698            Some(span),
10699        ));
10700    };
10701    let (Some(src), Some(base)) = (x.data.cast(t), y.data.cast(t)) else {
10702        return Err(Error::new(
10703            ErrorKind::Type,
10704            "the replacement and the argument hold different kinds of value",
10705            Some(span),
10706        ));
10707    };
10708    // Rebuild rather than mutate: the buffer may be shared, or foreign.
10709    let mut data = Data::empty(t);
10710    let mut plan: Vec<Option<usize>> = vec![None; y.items()];
10711    for (n, &k) in at.iter().enumerate() {
10712        plan[k] = Some(if per_index { n } else { 0 });
10713    }
10714    for (i, slot) in plan.iter().enumerate() {
10715        match slot {
10716            Some(n) => {
10717                for e in 0..cell {
10718                    push_elem(&mut data, &src, n * cell + e);
10719                }
10720            }
10721            None => {
10722                for e in 0..cell {
10723                    push_elem(&mut data, &base, i * cell + e);
10724                }
10725            }
10726        }
10727    }
10728    Ok(Array::new(y.shape.clone(), data))
10729}
10730
10731/// `x {:: y` (J): follow the path `x` into `y`, opening one level a step.
10732///
10733/// A boxed `x` is one step per box; a simple `x` is a single step, so
10734/// `1 {:: y` is item 1 of y opened once.
10735fn fetch(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
10736    let steps: Vec<Array> = match x.as_boxes() {
10737        Some(bs) => bs.to_vec(),
10738        None => vec![x.clone()],
10739    };
10740    let mut cur = y.clone();
10741    for step in steps {
10742        // An empty step selects the level whole, which is how a path
10743        // reaches into a boxed scalar; `a:` spells it and holds characters.
10744        let idx = if step.count() == 0 {
10745            Vec::new()
10746        } else {
10747            step.to_i64_vec_near(near)
10748                .ok_or_else(|| Error::domain("a fetch path holds integers", span))?
10749        };
10750        // A scalar has one item, which is how `{` reads one too.
10751        let base =
10752            if cur.rank() == 0 { Array::new(vec![1], cur.data.clone()) } else { cur.clone() };
10753        if idx.len() > base.rank() {
10754            return Err(Error::new(
10755                ErrorKind::Length,
10756                format!(
10757                    "a path step of {} index(es) into a value of rank {}",
10758                    idx.len(),
10759                    cur.rank()
10760                ),
10761                Some(span),
10762            ));
10763        }
10764        let at = cell_index(&base, &idx, span)?;
10765        cur = open_cell(&base.cell_at(idx.len(), at));
10766    }
10767    Ok(cur)
10768}
10769
10770/// The cell number a path step names, in the order `cell_at` counts them.
10771fn cell_index(y: &Array, idx: &[i64], span: Span) -> Result<usize> {
10772    let mut at = 0usize;
10773    for (k, &i) in idx.iter().enumerate() {
10774        let len = y.shape[k] as i64;
10775        let j = if i < 0 { i + len } else { i };
10776        if j < 0 || j >= len {
10777            return Err(Error::domain(
10778                format!("index {i} is out of range: axis {k} has {len} items"),
10779                span,
10780            ));
10781        }
10782        at = at * y.shape[k] + j as usize;
10783    }
10784    Ok(at)
10785}
10786
10787// ------------------------------------------------------ partition, groups
10788
10789/// `x ⊂ y` (APL2): partitioned enclose.
10790///
10791/// A partition opens wherever `x` rises — `x[i] > x[i-1]`, reading `x[-1]`
10792/// as zero — and an item whose flag is zero is dropped rather than joined
10793/// to anything. That is what GNU APL answers, and it is what makes
10794/// `1 1 2 2 ⊂ 'abcd'` two pairs rather than one run.
10795/// `x⊂y` in the Dyalog line: a partitioned enclose.
10796///
10797/// Each item of x says how many partitions to open before the item of y
10798/// beside it, so a count above one leaves an empty partition behind and a
10799/// leading zero drops the items ahead of the first partition. The answer
10800/// is a VECTOR of partitions however deep y is: rank 2 and above splits
10801/// the last axis and every partition keeps the axes ahead of it.
10802fn partition_counts(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
10803    if y.rank() == 0 {
10804        return Err(Error::new(
10805            ErrorKind::Rank,
10806            "partitioned enclose needs an array to partition",
10807            Some(span),
10808        ));
10809    }
10810    let counts = x
10811        .to_i64_vec_near(near)
10812        .ok_or_else(|| Error::domain("partition counts must be integers", span))?;
10813    if counts.iter().any(|&c| c < 0) {
10814        return Err(Error::domain("partition counts must not be negative", span));
10815    }
10816    let last = y.shape[y.rank() - 1];
10817    // A scalar count applies to every item; a vector shorter than the
10818    // axis is padded with zeros, so its items stay in the partition
10819    // already open. More counts than items is a length error.
10820    if counts.len() > last {
10821        return Err(Error::new(
10822            ErrorKind::Length,
10823            format!("{} count(s) for {} item(s)", counts.len(), last),
10824            Some(span),
10825        ));
10826    }
10827    let at = |i: usize| -> i64 {
10828        if x.rank() == 0 {
10829            counts.first().copied().unwrap_or(0)
10830        } else {
10831            counts.get(i).copied().unwrap_or(0)
10832        }
10833    };
10834    // Each partition is a contiguous run of the last axis: where it
10835    // starts, and how many items it holds.
10836    let mut groups: Vec<(usize, usize)> = Vec::new();
10837    for i in 0..last {
10838        for _ in 0..at(i) {
10839            groups.push((i, 0));
10840        }
10841        if let Some(g) = groups.last_mut() {
10842            g.1 += 1;
10843        }
10844    }
10845    let y = y.to_row_major();
10846    let rows = if last == 0 { 0 } else { y.count() / last };
10847    let lead = &y.shape[..y.rank() - 1];
10848    let parts: Vec<Array> = groups
10849        .iter()
10850        .map(|&(start, len)| {
10851            let mut d = Data::empty(y.dtype());
10852            for r in 0..rows {
10853                for c in start..start + len {
10854                    push_elem(&mut d, y.row_major_data(), r * last + c);
10855                }
10856            }
10857            let mut shape = lead.to_vec();
10858            shape.push(len);
10859            Array::new(shape, d)
10860        })
10861        .collect();
10862    Ok(Array::new(vec![parts.len()], Data::Box(parts.into())))
10863}
10864
10865fn partition_enclose(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
10866    // Rank 2 and above partitions the LAST axis, once per cross section,
10867    // so the axes ahead of it frame the answer.
10868    if y.rank() > 1 {
10869        let last = y.shape[y.rank() - 1];
10870        let rows = y.count() / last.max(1);
10871        let mut cells: Vec<Array> = Vec::new();
10872        let mut width = None;
10873        for r in 0..rows {
10874            let row = Array::new(vec![last], y.data.slice(r * last, (r + 1) * last));
10875            let parts = partition_enclose(x, &row, near, span)?;
10876            let n = parts.count();
10877            if *width.get_or_insert(n) != n {
10878                return Err(Error::internal("partitions of unequal count"));
10879            }
10880            match parts.data {
10881                Data::Box(v) => cells.extend(v.as_slice().iter().cloned()),
10882                _ => return Err(Error::internal("a partition is boxed")),
10883            }
10884        }
10885        let mut shape = y.shape[..y.rank() - 1].to_vec();
10886        shape.push(width.unwrap_or(0));
10887        return Ok(Array::new(shape, Data::Box(cells.into())));
10888    }
10889    if y.rank() == 0 {
10890        return Err(Error::new(
10891            ErrorKind::Rank,
10892            "partitioned enclose needs an array to partition",
10893            Some(span),
10894        ));
10895    }
10896    // No flag and no item: nothing is ever partitioned, so nothing about
10897    // the flags has to be a flag. `(0⍴⊂⍳3)⊂(0⍴0)` is the empty nested
10898    // vector. Where there ARE items, the flags are read as always — an
10899    // empty flag list against three items stays a length error.
10900    if x.count() == 0 && y.count() == 0 {
10901        return Ok(Array::new(vec![0], Data::Box(Vec::new().into())));
10902    }
10903    let mut flags = x
10904        .to_i64_vec_near(near)
10905        .ok_or_else(|| Error::domain("partition flags must be integers", span))?;
10906    if flags.iter().any(|&f| f < 0) {
10907        return Err(Error::domain("partition flags must not be negative", span));
10908    }
10909    // A SINGLE flag is the flag of every item, so `1⊂1 2 3` opens one
10910    // partition over the whole vector and `0⊂1 2 3` opens none. Only the
10911    // one-flag case extends: two flags for three items stays a length
10912    // error, since there is no reading that makes them fit.
10913    if flags.len() == 1 && y.shape[0] != 1 {
10914        flags = vec![flags[0]; y.shape[0]];
10915    }
10916    if flags.len() != y.shape[0] {
10917        return Err(Error::new(
10918            ErrorKind::Length,
10919            format!("{} flag(s) for {} item(s)", flags.len(), y.shape[0]),
10920            Some(span),
10921        ));
10922    }
10923    let mut parts: Vec<Array> = Vec::new();
10924    let mut cur: Option<Data> = None;
10925    let mut prev = 0i64;
10926    for (i, &f) in flags.iter().enumerate() {
10927        if f > prev {
10928            if let Some(d) = cur.take() {
10929                parts.push(Array::new(vec![d.len()], d));
10930            }
10931            cur = Some(Data::empty(y.dtype()));
10932        }
10933        prev = f;
10934        if f == 0 {
10935            continue;
10936        }
10937        if let Some(d) = cur.as_mut() {
10938            push_elem(d, &y.data, i);
10939        }
10940    }
10941    if let Some(d) = cur.take() {
10942        parts.push(Array::new(vec![d.len()], d));
10943    }
10944    Ok(Array::new(vec![parts.len()], Data::Box(parts.into())))
10945}
10946
10947/// `x u/. y` (J): `u` over each group of items of `y` sharing a key in `x`,
10948/// the groups in the order their keys first appear.
10949fn key(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
10950    let keys = if x.rank() == 0 { Array::new(vec![1], x.data.clone()) } else { x.clone() };
10951    let n = keys.items();
10952    if n != y.items() && !(y.rank() == 0 && n == 1) {
10953        return Err(Error::new(
10954            ErrorKind::Length,
10955            format!("{n} key(s) for {} item(s)", y.items()),
10956            Some(span),
10957        ));
10958    }
10959    let groups = group_positions(&keys, ctx.cfg.tol);
10960    let items = if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() };
10961    let mut cells = Vec::with_capacity(groups.len());
10962    for (_, at) in &groups {
10963        cells.push(u.monad(&select_items(&items, at), ctx, span)?);
10964    }
10965    assemble(&[groups.len()], cells, span)
10966}
10967
10968/// `u/. y` (J): `u` over each anti-diagonal of a table, starting at the
10969/// leading corner.
10970fn oblique(u: &Verb, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
10971    if y.rank() < 2 {
10972        let items = if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() };
10973        let n = items.items();
10974        let mut cells = Vec::with_capacity(n);
10975        for i in 0..n {
10976            cells.push(u.monad(&select_items(&items, &[i]), ctx, span)?);
10977        }
10978        return assemble(&[n], cells, span);
10979    }
10980    if y.rank() > 2 {
10981        return Err(Error::not_yet("oblique (u/.) on a rank-3 or higher argument", span));
10982    }
10983    let (rows, cols) = (y.shape[0], y.shape[1]);
10984    let mut cells = Vec::with_capacity(rows + cols - 1);
10985    for d in 0..rows + cols - 1 {
10986        let mut data = Data::empty(y.dtype());
10987        let mut len = 0usize;
10988        for i in 0..rows {
10989            if d >= i && d - i < cols {
10990                push_elem(&mut data, &y.data, i * cols + (d - i));
10991                len += 1;
10992            }
10993        }
10994        cells.push(u.monad(&Array::new(vec![len], data), ctx, span)?);
10995    }
10996    assemble(&[rows + cols - 1], cells, span)
10997}
10998
10999// ----------------------------------------------------------------- cutting
11000
11001/// Where each interval of a cut begins and ends (both inclusive of the
11002/// start, exclusive of the end).
11003///
11004/// `mode` is J's: 1 and -1 have the fret open an interval, 2 and -2 have it
11005/// close one, and the negative spellings drop the fret itself.
11006fn cut_ranges(frets: &[bool], mode: i64) -> Vec<(usize, usize)> {
11007    let n = frets.len();
11008    let mut out = Vec::new();
11009    if mode.abs() == 1 {
11010        let mut start: Option<usize> = None;
11011        for (i, &fret) in frets.iter().enumerate() {
11012            if fret {
11013                if let Some(s) = start {
11014                    out.push((s, i));
11015                }
11016                start = Some(i);
11017            }
11018        }
11019        if let Some(s) = start {
11020            out.push((s, n));
11021        }
11022        if mode < 0 {
11023            return out.into_iter().map(|(s, e)| (s + 1, e)).collect();
11024        }
11025    } else {
11026        let mut start = 0usize;
11027        for (i, &fret) in frets.iter().enumerate() {
11028            if fret {
11029                out.push((start, i + 1));
11030                start = i + 1;
11031            }
11032        }
11033        if mode < 0 {
11034            return out.into_iter().map(|(s, e)| (s, e - 1)).collect();
11035        }
11036    }
11037    out
11038}
11039
11040/// `x u;.n y` and `u;.n y` (J).
11041fn cut(
11042    u: &Verb,
11043    x: Option<&Array>,
11044    y: &Array,
11045    mode: i64,
11046    ctx: &mut Ctx<'_>,
11047    span: Span,
11048) -> Result<Array> {
11049    if mode == 0 {
11050        let Some(x) = x else {
11051            return u.monad(&reverse_all_axes(y), ctx, span);
11052        };
11053        let (origin, size) = rectangle(x, span)?;
11054        let origin = origin.unwrap_or_else(|| vec![0; size.len()]);
11055        return u.monad(&subarray(y, &origin, &size, span)?, ctx, span);
11056    }
11057    if mode.abs() == 3 {
11058        let Some(x) = x else {
11059            return Err(Error::not_yet("monadic tessellation (u;.3 y)", span));
11060        };
11061        return tessellate(u, x, y, mode < 0, ctx, span);
11062    }
11063    if !matches!(mode, 1 | -1 | 2 | -2) {
11064        return Err(Error::not_yet(format!("cut (u;.{mode})"), span));
11065    }
11066    let items = if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() };
11067    let n = items.items();
11068    let tol = ctx.cfg.tol;
11069    let frets: Vec<bool> = match x {
11070        Some(x) => {
11071            let flags = x.to_i64_vec().ok_or_else(|| {
11072                if x.dtype() == DType::Box {
11073                    // A boxed left argument is J's per-axis form, one box of
11074                    // frets per leading axis. Saying so is the contract:
11075                    // this is a gap, not a domain.
11076                    Error::not_yet("per-axis cut frets (a boxed left argument)", span)
11077                } else {
11078                    Error::domain("cut frets must be integers", span)
11079                }
11080            })?;
11081            // A fret is a flag, and only 0 and 1 are flags: `2 u;.1 y` is
11082            // a domain error, as the reference has it.
11083            if let Some(&bad) = flags.iter().find(|&&f| f != 0 && f != 1) {
11084                return Err(Error::domain(format!("{bad} is not a fret: a fret is 0 or 1"), span));
11085            }
11086            // A scalar fret marks every item, which is the whole of
11087            // `1 u;.2 y`: one interval per item.
11088            if x.rank() == 0 {
11089                vec![flags[0] != 0; n]
11090            } else if flags.is_empty() {
11091                // Marked below: no fret at all is the whole argument.
11092                Vec::new()
11093            } else {
11094                if flags.len() != n {
11095                    return Err(Error::new(
11096                        ErrorKind::Length,
11097                        format!("{} fret(s) for {n} item(s)", flags.len()),
11098                        Some(span),
11099                    ));
11100                }
11101                flags.iter().map(|&f| f != 0).collect()
11102            }
11103        }
11104        None => {
11105            // The fret is the argument's own first or last item.
11106            if n == 0 {
11107                Vec::new()
11108            } else {
11109                let at = if mode.abs() == 1 { 0 } else { n - 1 };
11110                let mark = items.item(at);
11111                (0..n).map(|i| arrays_match(&items.item(i), &mark, tol)).collect()
11112            }
11113        }
11114    };
11115    // A fret list with no frets in it marks nothing, and J reads that as the
11116    // whole argument in ONE piece — `(0$0) <;.1 'abc'` is one box of 'abc',
11117    // and with no fret to drop the negative spellings answer the same piece.
11118    // An argument with no item of its own still has no piece at all.
11119    // A fret list of a higher rank is J's per-axis form, one row of frets
11120    // per leading axis of y, and an empty one names no axis and no piece.
11121    let empty_frets = matches!(x, Some(x) if x.rank() == 1 && x.count() == 0);
11122    let no_axis = matches!(x, Some(x) if x.rank() > 1 && x.count() == 0);
11123    let ranges = if empty_frets && n > 0 {
11124        vec![(0, n)]
11125    } else if empty_frets || no_axis {
11126        Vec::new()
11127    } else {
11128        cut_ranges(&frets, mode)
11129    };
11130    // No frets, so no intervals: the one interval an empty argument offers
11131    // is the empty itself, and the verb applied to it says what shape the
11132    // pieces would have had.
11133    if ranges.is_empty() {
11134        let cell = u.is_pure().then(|| section(&items, 0, 0));
11135        return Ok(empty_frame(&[0], items.dtype(), cell, ctx, |cell, c| u.monad(cell, c, span)));
11136    }
11137    let mut cells = Vec::with_capacity(ranges.len());
11138    for (s, e) in &ranges {
11139        cells.push(u.monad(&section(&items, *s, *e), ctx, span)?);
11140    }
11141    assemble(&[ranges.len()], cells, span)
11142}
11143
11144/// The left argument of `;.0` and `;.3`: one row of origins (or movements)
11145/// and one of sizes. A single vector gives only the sizes.
11146fn rectangle(x: &Array, span: Span) -> Result<(Option<Vec<i64>>, Vec<i64>)> {
11147    let values = x
11148        .to_i64_vec()
11149        .ok_or_else(|| Error::domain("a cut rectangle is whole numbers", span))?;
11150    match x.rank() {
11151        0 | 1 => Ok((None, values)),
11152        2 if x.shape[0] == 2 => {
11153            let n = x.shape[1];
11154            Ok((Some(values[..n].to_vec()), values[n..].to_vec()))
11155        }
11156        _ => Err(Error::new(
11157            ErrorKind::Rank,
11158            "a cut rectangle is a vector of sizes, or two rows of origins and sizes",
11159            Some(span),
11160        )),
11161    }
11162}
11163
11164/// The block of `y` that starts at `origin` and runs `size` along each of
11165/// the leading axes, the rest of them taken whole. A negative size runs the
11166/// same distance and reverses that axis.
11167fn subarray(y: &Array, origin: &[i64], size: &[i64], span: Span) -> Result<Array> {
11168    if origin.len() > y.rank() {
11169        return Err(Error::new(
11170            ErrorKind::Rank,
11171            format!("a cut of {} axis/axes into a rank-{} value", origin.len(), y.rank()),
11172            Some(span),
11173        ));
11174    }
11175    let r = y.rank();
11176    let st = strides(&y.shape);
11177    let mut shape = y.shape.clone();
11178    let mut start = vec![0i64; r];
11179    let mut step = vec![1i64; r];
11180    for k in 0..origin.len() {
11181        // The magnitude is measured in u128 so that a size of i64::MIN — a
11182        // number the program is free to write — is compared rather than
11183        // negated, and the axis check runs before anything is cast down.
11184        let want = u128::from(size[k].unsigned_abs());
11185        let from = if origin[k] < 0 { origin[k] + y.shape[k] as i64 } else { origin[k] };
11186        if from < 0 || u128::from(from.unsigned_abs()) + want > y.shape[k] as u128 {
11187            return Err(Error::domain(
11188                format!("a cut of {want} from {from} leaves axis {k} of {}", y.shape[k]),
11189                span,
11190            ));
11191        }
11192        let len = want as usize;
11193        shape[k] = len;
11194        if size[k] < 0 {
11195            start[k] = from + len as i64 - 1;
11196            step[k] = -1;
11197        } else {
11198            start[k] = from;
11199        }
11200    }
11201    Ok(gather(y, &shape, &start, &step, &st))
11202}
11203
11204/// The elements of `y` at `start + step × coordinate`, shaped `shape`.
11205fn gather(y: &Array, shape: &[usize], start: &[i64], step: &[i64], st: &[usize]) -> Array {
11206    let n: usize = shape.iter().product();
11207    let mut data = Data::empty(y.dtype());
11208    let mut coord = vec![0usize; shape.len()];
11209    for _ in 0..n {
11210        let idx: usize = (0..shape.len())
11211            .map(|k| (start[k] + step[k] * coord[k] as i64) as usize * st[k])
11212            .sum();
11213        push_elem(&mut data, &y.data, idx);
11214        odometer(&mut coord, shape);
11215    }
11216    Array::new(shape.to_vec(), data)
11217}
11218
11219/// `x u;.3 y` and `x u;._3 y`: u over every block of the given size, moved
11220/// by the given step along each axis. `;.3` keeps the short blocks at the
11221/// far edge; `;._3` takes only the complete ones.
11222fn tessellate(
11223    u: &Verb,
11224    x: &Array,
11225    y: &Array,
11226    complete: bool,
11227    ctx: &mut Ctx<'_>,
11228    span: Span,
11229) -> Result<Array> {
11230    // A single vector gives the sizes; the blocks then move one at a time.
11231    let (movement, size) = rectangle(x, span)?;
11232    // A negative size reverses its axis, which is well defined only where
11233    // the movement is written out: given a bare vector of sizes the
11234    // reference answers with something the magnitude plays no part in, and
11235    // libjay will not guess at it.
11236    if size.iter().any(|&s| s < 0) && movement.is_none() {
11237        return Err(Error::not_yet(
11238            "a negative block size without a movement row (x u;.3 y)",
11239            span,
11240        ));
11241    }
11242    let movement = movement.unwrap_or_else(|| vec![1; size.len()]);
11243    if size.len() > y.rank() {
11244        return Err(Error::new(
11245            ErrorKind::Rank,
11246            format!("a tessellation of {} axis/axes into a rank-{} value", size.len(), y.rank()),
11247            Some(span),
11248        ));
11249    }
11250    // The block size and the step are the program's own numbers and may be
11251    // any i64, so how many blocks fit is counted in i128: `size` has no
11252    // negation at i64::MIN and `len + step` overflows for a large step.
11253    let mut frame = Vec::with_capacity(size.len());
11254    for k in 0..size.len() {
11255        let (len, step) = (i128::from(y.shape[k] as i64), i128::from(movement[k]));
11256        let block = i128::from(size[k]).abs();
11257        if step <= 0 {
11258            return Err(Error::domain("a tessellation moves by a positive step", span));
11259        }
11260        let count = if complete {
11261            if len < block { 0 } else { (len - block) / step + 1 }
11262        } else {
11263            (len + step - 1) / step
11264        };
11265        frame.push(count as usize);
11266    }
11267    let total: usize = frame.iter().product();
11268    let mut cells = Vec::with_capacity(total);
11269    let mut coord = vec![0usize; frame.len()];
11270    for _ in 0..total {
11271        let origin: Vec<i64> = (0..frame.len()).map(|k| coord[k] as i64 * movement[k]).collect();
11272        // A block at the far edge is cut short by what is left of the axis;
11273        // a negative size keeps its sign, which reverses that axis.
11274        let block: Vec<i64> = (0..frame.len())
11275            .map(|k| {
11276                let left = i128::from(y.shape[k] as i64 - origin[k]);
11277                let len = i128::from(size[k]).abs().min(left) as i64;
11278                if size[k] < 0 { -len } else { len }
11279            })
11280            .collect();
11281        cells.push(u.monad(&subarray(y, &origin, &block, span)?, ctx, span)?);
11282        odometer(&mut coord, &frame);
11283    }
11284    assemble(&frame, cells, span)
11285}
11286
11287/// Every axis of `y` reversed — what `u;.0 y` applies its verb to.
11288fn reverse_all_axes(y: &Array) -> Array {
11289    if y.rank() == 0 {
11290        return y.clone();
11291    }
11292    let st = strides(&y.shape);
11293    let n = y.count();
11294    let r = y.rank();
11295    let mut data = Data::empty(y.dtype());
11296    let mut coord = vec![0usize; r];
11297    for _ in 0..n {
11298        let idx: usize = (0..r).map(|k| (y.shape[k] - 1 - coord[k]) * st[k]).sum();
11299        push_elem(&mut data, &y.data, idx);
11300        odometer(&mut coord, &y.shape);
11301    }
11302    Array::new(y.shape.clone(), data)
11303}
11304
11305// ------------------------------------------------------------ along an axis
11306
11307/// `y` with axis `k` moved in front of the others, their order kept.
11308fn axis_to_front(y: &Array, k: usize) -> Array {
11309    if k == 0 || y.rank() < 2 {
11310        return y.clone();
11311    }
11312    let r = y.rank();
11313    let src: Vec<usize> = std::iter::once(k).chain((0..r).filter(|&a| a != k)).collect();
11314    permute_axes(y, &src)
11315}
11316
11317/// `y` with its leading axis moved to position `k`.
11318fn front_to_axis(y: &Array, k: usize) -> Array {
11319    if k == 0 || y.rank() < 2 {
11320        return y.clone();
11321    }
11322    let r = y.rank();
11323    // Output axis a reads source axis: the ones before k shift up by one,
11324    // k itself is the source's leading axis, the rest keep their place.
11325    let mut src = Vec::with_capacity(r);
11326    for a in 0..r {
11327        src.push(match a.cmp(&k) {
11328            std::cmp::Ordering::Less => a + 1,
11329            std::cmp::Ordering::Equal => 0,
11330            std::cmp::Ordering::Greater => a,
11331        });
11332    }
11333    permute_axes(y, &src)
11334}
11335
11336/// `x |: y` and `x ⍉ y`: y with each of its axes sent where the left
11337/// argument says. Several axes sharing a destination are run together,
11338/// which is the diagonal, and the result is as long there as the shortest
11339/// of them.
11340fn transpose_to(y: &Array, dest: &[usize], span: Span) -> Result<Array> {
11341    let rank_out = dest.iter().copied().max().map_or(0, |m| m + 1);
11342    let mut out_shape = vec![usize::MAX; rank_out];
11343    for (a, &d) in dest.iter().enumerate() {
11344        out_shape[d] = out_shape[d].min(y.shape[a]);
11345    }
11346    if out_shape.contains(&usize::MAX) {
11347        return Err(Error::new(
11348            ErrorKind::Domain,
11349            "a transpose must name every axis of the result",
11350            Some(span),
11351        ));
11352    }
11353    let y = y.to_row_major();
11354    let st = strides(&y.shape);
11355    let n: usize = out_shape.iter().product();
11356    let mut data = Data::empty(y.dtype());
11357    let mut coord = vec![0usize; rank_out];
11358    for _ in 0..n {
11359        let idx: usize = dest.iter().enumerate().map(|(a, &d)| coord[d] * st[a]).sum();
11360        push_elem(&mut data, &y.data, idx);
11361        odometer(&mut coord, &out_shape);
11362    }
11363    Ok(Array::new(out_shape, data))
11364}
11365
11366/// `x ⍉ y`: x names, for each axis of y in turn, the axis of the result it
11367/// becomes. Two axes given the same destination are run together.
11368fn transpose_apl(x: &Array, y: &Array, io: i64, near: NearInt, span: Span) -> Result<Array> {
11369    let axes = x
11370        .to_i64_vec_near(near)
11371        .ok_or_else(|| Error::domain("a transpose is given whole numbers", span))?;
11372    if axes.len() != y.rank() {
11373        return Err(Error::new(
11374            ErrorKind::Length,
11375            format!("{} axes for a rank-{} value", axes.len(), y.rank()),
11376            Some(span),
11377        ));
11378    }
11379    let mut dest = Vec::with_capacity(axes.len());
11380    for a in axes {
11381        let d = a - io;
11382        if d < 0 || d as usize >= y.rank() {
11383            return Err(Error::new(
11384                ErrorKind::Domain,
11385                format!("axis {a} is outside a rank-{} value", y.rank()),
11386                Some(span),
11387            ));
11388        }
11389        dest.push(d as usize);
11390    }
11391    transpose_to(y, &dest, span)
11392}
11393
11394/// `x |: y`: x names the axes to move to the END, in the order given; the
11395/// rest keep their order in front. A boxed x groups axes, and the axes of
11396/// one group are run together — the diagonal.
11397fn transpose_j(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
11398    let groups: Vec<Vec<i64>> = match x.as_boxes() {
11399        Some(bs) => bs
11400            .iter()
11401            .map(|b| {
11402                b.to_i64_vec_near(near).ok_or_else(|| {
11403                    Error::domain("a transpose is given whole numbers", span)
11404                })
11405            })
11406            .collect::<Result<Vec<_>>>()?,
11407        None => x
11408            .to_i64_vec_near(near)
11409            .ok_or_else(|| Error::domain("a transpose is given whole numbers", span))?
11410            .into_iter()
11411            .map(|a| vec![a])
11412            .collect(),
11413    };
11414    let r = y.rank();
11415    // Which group each axis belongs to; an axis named twice is an error, as
11416    // it is in J.
11417    let mut group_of = vec![None; r];
11418    for (g, axes) in groups.iter().enumerate() {
11419        for &a in axes {
11420            let k = if a < 0 { a + r as i64 } else { a };
11421            if k < 0 || k as usize >= r {
11422                return Err(Error::new(
11423                    ErrorKind::Domain,
11424                    format!("axis {a} is outside a rank-{r} value"),
11425                    Some(span),
11426                ));
11427            }
11428            if group_of[k as usize].is_some() {
11429                return Err(Error::new(
11430                    ErrorKind::Domain,
11431                    format!("axis {a} is named twice in a transpose"),
11432                    Some(span),
11433                ));
11434            }
11435            group_of[k as usize] = Some(g);
11436        }
11437    }
11438    let leading = group_of.iter().filter(|g| g.is_none()).count();
11439    let mut dest = vec![0usize; r];
11440    let mut next = 0;
11441    for a in 0..r {
11442        match group_of[a] {
11443            None => {
11444                dest[a] = next;
11445                next += 1;
11446            }
11447            Some(g) => dest[a] = leading + g,
11448        }
11449    }
11450    transpose_to(y, &dest, span)
11451}
11452
11453/// `y` with output axis `a` reading source axis `src[a]`.
11454fn permute_axes(y: &Array, src: &[usize]) -> Array {
11455    let st = strides(&y.shape);
11456    let out_shape: Vec<usize> = src.iter().map(|&a| y.shape[a]).collect();
11457    let n = y.count();
11458    let mut data = Data::empty(y.dtype());
11459    let mut coord = vec![0usize; src.len()];
11460    for _ in 0..n {
11461        let idx: usize = (0..src.len()).map(|a| coord[a] * st[src[a]]).sum();
11462        push_elem(&mut data, &y.data, idx);
11463        odometer(&mut coord, &out_shape);
11464    }
11465    Array::new(out_shape, data)
11466}
11467
11468// ------------------------------------------------ index specifications
11469
11470/// What a J index specification picks out of an array.
11471struct Spec {
11472    /// How many leading axes of the argument the specification indexes.
11473    width: usize,
11474    /// One coordinate vector per selected cell, in result order.
11475    cells: Vec<Vec<usize>>,
11476    /// The shape the specification contributes; the argument's remaining
11477    /// axes follow it.
11478    shape: Vec<usize>,
11479}
11480
11481/// One index against an axis of `len` elements, counting a negative one
11482/// from the end.
11483fn axis_position(v: i64, len: usize, span: Span) -> Result<usize> {
11484    let p = if v < 0 { v + len as i64 } else { v };
11485    if p < 0 || p >= len as i64 {
11486        return Err(Error::domain(
11487            format!("index {v} is out of range: the axis has {len} element(s)"),
11488            span,
11489        ));
11490    }
11491    Ok(p as usize)
11492}
11493
11494/// J's index specification: what a BOXED left argument of `{` or `m}` says.
11495///
11496/// `<A` with a simple `A` reads A's last axis as one index per leading axis
11497/// of y, the axes ahead of it framing the result — so `(<1 2) { y` is one
11498/// element and `(<2 2$…) { y` is two of them. `<(c0;c1;…)` gives one
11499/// component per leading axis instead: a simple component's atoms are that
11500/// axis's indices, a scalar one dropping the axis from the result, and a
11501/// BOXED component is the complement — every index of the axis except the
11502/// ones it holds, which is what `a:` (the empty box) uses to mean "all".
11503fn index_spec(content: &Array, y: &Array, near: NearInt, span: Span) -> Result<Spec> {
11504    let too_deep = |n: usize| {
11505        Error::new(
11506            ErrorKind::Rank,
11507            format!("an index specification of {n} axis/axes into a rank-{} value", y.rank()),
11508            Some(span),
11509        )
11510    };
11511    if let Some(items) = content.as_boxes() {
11512        if items.len() > y.rank() {
11513            return Err(too_deep(items.len()));
11514        }
11515        let mut per_axis: Vec<Vec<usize>> = Vec::with_capacity(items.len());
11516        let mut shape: Vec<usize> = Vec::new();
11517        for (k, c) in items.iter().enumerate() {
11518            let len = y.shape[k];
11519            if c.as_boxes().is_some() {
11520                let inner = open_cell(c);
11521                let excluded = inner.to_i64_vec_near(near).ok_or_else(|| {
11522                    Error::domain("an index complement holds integers", span)
11523                })?;
11524                let mut dropped = vec![false; len];
11525                for v in excluded {
11526                    dropped[axis_position(v, len, span)?] = true;
11527                }
11528                let kept: Vec<usize> = (0..len).filter(|i| !dropped[*i]).collect();
11529                shape.push(kept.len());
11530                per_axis.push(kept);
11531            } else {
11532                let idx = c
11533                    .to_i64_vec_near(near)
11534                    .ok_or_else(|| Error::domain("an index holds integers", span))?;
11535                let mut positions = Vec::with_capacity(idx.len());
11536                for v in idx {
11537                    positions.push(axis_position(v, len, span)?);
11538                }
11539                shape.extend_from_slice(&c.shape);
11540                per_axis.push(positions);
11541            }
11542        }
11543        // The components run as an odometer, the last one fastest.
11544        let mut cells: Vec<Vec<usize>> = vec![Vec::new()];
11545        for positions in &per_axis {
11546            let mut next = Vec::with_capacity(cells.len() * positions.len());
11547            for prefix in &cells {
11548                for &p in positions {
11549                    let mut cell = prefix.clone();
11550                    cell.push(p);
11551                    next.push(cell);
11552                }
11553            }
11554            cells = next;
11555        }
11556        return Ok(Spec { width: per_axis.len(), cells, shape });
11557    }
11558    let idx = content
11559        .to_i64_vec_near(near)
11560        .ok_or_else(|| Error::domain("an index specification holds integers", span))?;
11561    let rank = content.rank();
11562    let width = if rank == 0 { 1 } else { content.shape[rank - 1] };
11563    if width > y.rank() {
11564        return Err(too_deep(width));
11565    }
11566    let shape: Vec<usize> = if rank == 0 { Vec::new() } else { content.shape[..rank - 1].to_vec() };
11567    let count: usize = shape.iter().product();
11568    let mut cells: Vec<Vec<usize>> = Vec::new();
11569    if width == 0 {
11570        cells.resize(count, Vec::new());
11571    } else {
11572        for chunk in idx.chunks(width) {
11573            let mut cell = Vec::with_capacity(width);
11574            for (k, &v) in chunk.iter().enumerate() {
11575                cell.push(axis_position(v, y.shape[k], span)?);
11576            }
11577            cells.push(cell);
11578        }
11579    }
11580    Ok(Spec { width, cells, shape })
11581}
11582
11583/// The offset of a cell's first element, given the argument's strides.
11584fn spec_offset(st: &[usize], cell: &[usize]) -> usize {
11585    cell.iter().enumerate().map(|(k, &p)| p * st[k]).sum()
11586}
11587
11588/// `(<spec) { y`: the cells the specification names, in its own order.
11589fn select_spec(spec: &Spec, y: &Array) -> Array {
11590    let st = strides(&y.shape);
11591    let size: usize = y.shape[spec.width..].iter().product();
11592    let mut data = Data::empty(y.dtype());
11593    for cell in &spec.cells {
11594        let base = spec_offset(&st, cell);
11595        for e in 0..size {
11596            push_elem(&mut data, &y.data, base + e);
11597        }
11598    }
11599    let mut shape = spec.shape.clone();
11600    shape.extend_from_slice(&y.shape[spec.width..]);
11601    Array::new(shape, data)
11602}
11603
11604/// `x (<spec)} y`: y with the cells the specification names replaced by x,
11605/// which is either one cell spread over all of them or one cell each.
11606fn amend_spec(spec: &Spec, x: &Array, y: &Array, span: Span) -> Result<Array> {
11607    let size: usize = y.shape[spec.width..].iter().product();
11608    let per_cell = if x.count() == size {
11609        false
11610    } else if x.count() == size * spec.cells.len() {
11611        true
11612    } else {
11613        return Err(Error::new(
11614            ErrorKind::Length,
11615            format!(
11616                "cannot amend {} cell(s) of {size} element(s) each with {} element(s)",
11617                spec.cells.len(),
11618                x.count()
11619            ),
11620            Some(span),
11621        ));
11622    };
11623    let mismatch = || {
11624        Error::new(
11625            ErrorKind::Type,
11626            "the replacement and the argument hold different kinds of value",
11627            Some(span),
11628        )
11629    };
11630    let t = DType::promote(x.dtype(), y.dtype()).ok_or_else(mismatch)?;
11631    let (Some(src), Some(base)) = (x.data.cast(t), y.data.cast(t)) else {
11632        return Err(mismatch());
11633    };
11634    let st = strides(&y.shape);
11635    let mut plan: Vec<Option<usize>> = vec![None; y.count()];
11636    for (n, cell) in spec.cells.iter().enumerate() {
11637        let at = spec_offset(&st, cell);
11638        for e in 0..size {
11639            plan[at + e] = Some(if per_cell { n * size + e } else { e });
11640        }
11641    }
11642    let mut data = Data::empty(t);
11643    for (i, slot) in plan.iter().enumerate() {
11644        match slot {
11645            Some(n) => push_elem(&mut data, &src, *n),
11646            None => push_elem(&mut data, &base, i),
11647        }
11648    }
11649    Ok(Array::new(y.shape.clone(), data))
11650}
11651
11652// -------------------------------------------------------------- the map
11653
11654/// J monadic `{::`: y's box structure with every leaf replaced by the path
11655/// that fetches it.
11656///
11657/// A path is a boxed list holding one index per level descended — the
11658/// coordinate vector within that level's array, empty where the level is a
11659/// boxed scalar. An unboxed y is one leaf, itself, and its path is empty.
11660fn map_paths(y: &Array) -> Array {
11661    fn coord_of(shape: &[usize], mut i: usize) -> Array {
11662        let mut out = vec![0i64; shape.len()];
11663        for k in (0..shape.len()).rev() {
11664            out[k] = (i % shape[k]) as i64;
11665            i /= shape[k];
11666        }
11667        Array::from_i64(out)
11668    }
11669    fn go(y: &Array, prefix: &[Array]) -> Array {
11670        let Some(boxes) = y.as_boxes() else {
11671            if prefix.is_empty() {
11672                return Array::new(vec![0], Data::I64(Vec::new().into()));
11673            }
11674            return Array::new(vec![prefix.len()], Data::Box(prefix.to_vec().into()));
11675        };
11676        let cells: Vec<Array> = boxes
11677            .iter()
11678            .enumerate()
11679            .map(|(i, b)| {
11680                let mut path = prefix.to_vec();
11681                path.push(coord_of(&y.shape, i));
11682                go(b, &path)
11683            })
11684            .collect();
11685        Array::new(y.shape.clone(), Data::Box(cells.into()))
11686    }
11687    go(y, &[])
11688}
11689
11690// ------------------------------------------------------- fill and shift
11691
11692/// `x |.!.f y`: shift along each axis instead of rotating, so an item moved
11693/// past an end is dropped and the place it left takes the fill f.
11694fn shift_fill(
11695    x: &Array,
11696    y: &Array,
11697    fill: &Array,
11698    near: NearInt,
11699    span: Span,
11700) -> Result<Array> {
11701    let counts = axis_counts(x, "shift", near, span)?;
11702    if y.rank() == 0 {
11703        return Ok(y.clone());
11704    }
11705    if counts.len() > y.rank() {
11706        return Err(Error::new(
11707            ErrorKind::Length,
11708            format!("shift has {} amounts for an argument of rank {}", counts.len(), y.rank()),
11709            Some(span),
11710        ));
11711    }
11712    if fill.count() != 1 {
11713        return Err(Error::new(ErrorKind::Length, "a fill is one atom", Some(span)));
11714    }
11715    let mismatch = || {
11716        Error::new(ErrorKind::Type, "the fill and the argument differ in kind", Some(span))
11717    };
11718    let t = DType::promote(y.dtype(), fill.dtype()).ok_or_else(mismatch)?;
11719    let (Some(base), Some(f)) = (y.data.cast(t), fill.data.cast(t)) else {
11720        return Err(mismatch());
11721    };
11722    let st = strides(&y.shape);
11723    let r = y.rank();
11724    let mut data = Data::empty(t);
11725    let mut coord = vec![0usize; r];
11726    for _ in 0..y.count() {
11727        let mut idx = 0usize;
11728        let mut vacated = false;
11729        for k in 0..r {
11730            // Saturating: an amount that cannot be added to the coordinate
11731            // has carried the item past the end of the axis by any measure,
11732            // which is what a shift vacates.
11733            let from = (coord[k] as i64).saturating_add(counts.get(k).copied().unwrap_or(0));
11734            if from < 0 || from >= y.shape[k] as i64 {
11735                vacated = true;
11736                break;
11737            }
11738            idx += from as usize * st[k];
11739        }
11740        if vacated {
11741            push_elem(&mut data, &f, 0);
11742        } else {
11743            push_elem(&mut data, &base, idx);
11744        }
11745        odometer(&mut coord, &y.shape);
11746    }
11747    Ok(Array::new(y.shape.clone(), data))
11748}
11749
11750// ---------------------------------------------------------------- memo
11751
11752/// An exact key for one array, appended to `out`. False where the value has
11753/// no cheap key — an exact number — and the memo must simply not cache it.
11754fn memo_key(a: &Array, out: &mut Vec<u64>) -> bool {
11755    out.push(a.rank() as u64);
11756    out.extend(a.shape.iter().map(|&n| n as u64));
11757    out.push(a.dtype() as u64);
11758    match &a.data {
11759        Data::Ext(_) | Data::Rat(_) => false,
11760        Data::Box(items) => items.iter().all(|item| memo_key(item, out)),
11761        d => {
11762            for i in 0..d.len() {
11763                out.push(elem_key(d, i));
11764            }
11765            true
11766        }
11767    }
11768}
11769
11770/// `u M.`: u's answer for these arguments, computed once and kept.
11771fn memoised(
11772    u: &Verb,
11773    cache: &MemoCache,
11774    x: Option<&Array>,
11775    y: &Array,
11776    ctx: &mut Ctx<'_>,
11777    span: Span,
11778) -> Result<Array> {
11779    let apply = |ctx: &mut Ctx<'_>| match x {
11780        Some(x) => u.dyad(x, y, ctx, span),
11781        None => u.monad(y, ctx, span),
11782    };
11783    let mut key = vec![u64::from(x.is_some())];
11784    let keyed = x.is_none_or(|x| memo_key(x, &mut key)) && memo_key(y, &mut key);
11785    if !keyed {
11786        return apply(ctx);
11787    }
11788    if let Ok(map) = cache.lock() && let Some(hit) = map.get(&key) {
11789        return Ok(hit.clone());
11790    }
11791    let out = apply(ctx)?;
11792    if let Ok(mut map) = cache.lock() {
11793        map.insert(key, out.clone());
11794    }
11795    Ok(out)
11796}
11797
11798// ----------------------------------------------------- levels and spread
11799
11800/// `u L: n y` and `u S: n y`: u over every subarray at boxing level n or
11801/// below. `L:` puts each answer back where its operand was; `S:` collects
11802/// them into the items of one array.
11803fn at_level(
11804    u: &Verb,
11805    level: i64,
11806    spread: bool,
11807    y: &Array,
11808    ctx: &mut Ctx<'_>,
11809    span: Span,
11810) -> Result<Array> {
11811    // A negative level counts down from the argument's own top.
11812    let n = if level < 0 { (boxing_level(y) + level).max(0) } else { level };
11813    if !spread {
11814        return map_level(u, n, y, ctx, span);
11815    }
11816    let mut cells = Vec::new();
11817    collect_level(u, n, y, ctx, span, &mut cells)?;
11818    let count = cells.len();
11819    assemble(&[count], cells, span)
11820}
11821
11822fn map_level(u: &Verb, n: i64, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
11823    let Some(boxes) = y.as_boxes().filter(|_| boxing_level(y) > n) else {
11824        return u.monad(y, ctx, span);
11825    };
11826    let boxes = boxes.to_vec();
11827    let mut cells = Vec::with_capacity(boxes.len());
11828    for b in &boxes {
11829        cells.push(map_level(u, n, b, ctx, span)?);
11830    }
11831    Ok(Array::new(y.shape.clone(), Data::Box(cells.into())))
11832}
11833
11834/// `x u L: n y` and `x u S: n y`: both arguments are descended together
11835/// until each has reached level n, and u is applied to the pair. A side
11836/// that has already reached its level is held while the other descends, so
11837/// an unboxed left argument reaches every leaf of the right one.
11838fn at_level_dyad(
11839    u: &Verb,
11840    level: i64,
11841    spread: bool,
11842    x: &Array,
11843    y: &Array,
11844    ctx: &mut Ctx<'_>,
11845    span: Span,
11846) -> Result<Array> {
11847    // A negative level counts down from each argument's own top, so the
11848    // two sides can stop at different depths.
11849    let depth = |a: &Array| if level < 0 { (boxing_level(a) + level).max(0) } else { level };
11850    let (nx, ny) = (depth(x), depth(y));
11851    if !spread {
11852        return map_level_dyad(u, nx, ny, x, y, ctx, span);
11853    }
11854    let mut cells = Vec::new();
11855    collect_level_dyad(u, nx, ny, x, y, ctx, span, &mut cells)?;
11856    let count = cells.len();
11857    assemble(&[count], cells, span)
11858}
11859
11860/// The boxes to descend into on each side, and the shape the answer takes.
11861struct LevelPairs {
11862    left: Vec<Array>,
11863    right: Vec<Array>,
11864    shape: Vec<usize>,
11865}
11866
11867/// One step of the descent. `None` where neither side has any box left,
11868/// which is where u applies.
11869fn level_pairs(
11870    nx: i64,
11871    ny: i64,
11872    x: &Array,
11873    y: &Array,
11874    span: Span,
11875) -> Result<Option<LevelPairs>> {
11876    let bx = x.as_boxes().filter(|_| boxing_level(x) > nx);
11877    let by = y.as_boxes().filter(|_| boxing_level(y) > ny);
11878    Ok(match (bx, by) {
11879        (None, None) => None,
11880        (Some(bx), None) => {
11881            let n = bx.len();
11882            Some(LevelPairs {
11883                left: bx.to_vec(),
11884                right: vec![y.clone(); n],
11885                shape: x.shape.clone(),
11886            })
11887        }
11888        (None, Some(by)) => {
11889            let n = by.len();
11890            Some(LevelPairs {
11891                left: vec![x.clone(); n],
11892                right: by.to_vec(),
11893                shape: y.shape.clone(),
11894            })
11895        }
11896        (Some(bx), Some(by)) => {
11897            if x.shape != y.shape {
11898                return Err(Error::new(
11899                    ErrorKind::Length,
11900                    format!(
11901                        "the levels do not agree: left shape {}, right shape {}",
11902                        show_shape(&x.shape),
11903                        show_shape(&y.shape)
11904                    ),
11905                    Some(span),
11906                ));
11907            }
11908            Some(LevelPairs { left: bx.to_vec(), right: by.to_vec(), shape: x.shape.clone() })
11909        }
11910    })
11911}
11912
11913fn map_level_dyad(
11914    u: &Verb,
11915    nx: i64,
11916    ny: i64,
11917    x: &Array,
11918    y: &Array,
11919    ctx: &mut Ctx<'_>,
11920    span: Span,
11921) -> Result<Array> {
11922    let Some(step) = level_pairs(nx, ny, x, y, span)? else {
11923        return u.dyad(x, y, ctx, span);
11924    };
11925    let mut cells = Vec::with_capacity(step.left.len());
11926    for (a, b) in step.left.iter().zip(step.right.iter()) {
11927        cells.push(map_level_dyad(u, nx, ny, a, b, ctx, span)?);
11928    }
11929    Ok(Array::new(step.shape, Data::Box(cells.into())))
11930}
11931
11932#[allow(clippy::too_many_arguments)]
11933fn collect_level_dyad(
11934    u: &Verb,
11935    nx: i64,
11936    ny: i64,
11937    x: &Array,
11938    y: &Array,
11939    ctx: &mut Ctx<'_>,
11940    span: Span,
11941    out: &mut Vec<Array>,
11942) -> Result<()> {
11943    let Some(step) = level_pairs(nx, ny, x, y, span)? else {
11944        out.push(u.dyad(x, y, ctx, span)?);
11945        return Ok(());
11946    };
11947    for (a, b) in step.left.iter().zip(step.right.iter()) {
11948        collect_level_dyad(u, nx, ny, a, b, ctx, span, out)?;
11949    }
11950    Ok(())
11951}
11952
11953fn collect_level(
11954    u: &Verb,
11955    n: i64,
11956    y: &Array,
11957    ctx: &mut Ctx<'_>,
11958    span: Span,
11959    out: &mut Vec<Array>,
11960) -> Result<()> {
11961    let Some(boxes) = y.as_boxes().filter(|_| boxing_level(y) > n) else {
11962        out.push(u.monad(y, ctx, span)?);
11963        return Ok(());
11964    };
11965    let boxes = boxes.to_vec();
11966    for b in &boxes {
11967        collect_level(u, n, b, ctx, span, out)?;
11968    }
11969    Ok(())
11970}
11971
11972// --------------------------------------------------------- polynomials
11973
11974/// The ascending coefficients of a polynomial argument, as complex values.
11975fn poly_coeffs(y: &Array, span: Span) -> Result<Vec<Cx>> {
11976    let c = y
11977        .data
11978        .cast(DType::Complex)
11979        .ok_or_else(|| Error::domain("a polynomial's coefficients are numbers", span))?;
11980    match c {
11981        Data::Complex(v) => Ok(v.as_slice().to_vec()),
11982        _ => Err(Error::internal("coefficients did not cast to complex")),
11983    }
11984}
11985
11986/// The same, where an argument with no elements is no coefficient at all
11987/// rather than a type to refuse. A polynomial with no coefficients is the
11988/// zero one and a root form with no roots is its multiplier, so `p. (0$'a')`
11989/// and `(1;0$'a') p. 4` both answer. J keeps the strict reading for the
11990/// integral's argument, which is why the two live side by side.
11991fn poly_coeffs_relaxed(y: &Array, span: Span) -> Result<Vec<Cx>> {
11992    if y.count() == 0 {
11993        return Ok(Vec::new());
11994    }
11995    poly_coeffs(y, span)
11996}
11997
11998/// The ascending coefficients a boxed root form stands for: `m × (x-r0) ×
11999/// (x-r1) × …`, multiplied out.
12000fn root_form_coeffs(parts: &[Array], span: Span) -> Result<Vec<Cx>> {
12001    let (multiplier, roots) = root_form(parts, span)?;
12002    let mut coeffs = vec![multiplier];
12003    for r in poly_coeffs_relaxed(roots, span)? {
12004        let mut next = vec![cx::ZERO; coeffs.len() + 1];
12005        for (k, &c) in coeffs.iter().enumerate() {
12006            next[k + 1] = cx::add(next[k + 1], c);
12007            next[k] = cx::sub(next[k], cx::mul(c, r));
12008        }
12009        coeffs = next;
12010    }
12011    Ok(coeffs)
12012}
12013
12014/// The multiplier and the roots a boxed polynomial argument holds. J writes
12015/// the form as `multiplier ; roots` and lets the multiplier go unsaid: one
12016/// box is the roots alone, with a multiplier of 1.
12017fn root_form(parts: &[Array], span: Span) -> Result<(Cx, &Array)> {
12018    match parts {
12019        [roots] => Ok((cx::ONE, roots)),
12020        [multiplier, roots] => Ok((
12021            poly_coeffs_relaxed(multiplier, span)?.first().copied().unwrap_or(cx::ONE),
12022            roots,
12023        )),
12024        _ => Err(Error::domain("the root form of a polynomial is `multiplier ; roots`", span)),
12025    }
12026}
12027
12028// --------------------------------------------------- hypergeometric series
12029
12030/// Terms the series is allowed before it is called divergent.
12031const HYPERGEOMETRIC_TERMS: usize = 1 << 16;
12032
12033/// A parameter list, for a derived verb's name.
12034fn cx_list(v: &[Cx]) -> String {
12035    v.iter()
12036        .map(|z| if z[1] == 0.0 { format!("{}", z[0]) } else { format!("{}j{}", z[0], z[1]) })
12037        .collect::<Vec<_>>()
12038        .join(" ")
12039}
12040
12041/// `(m H. n) y`: the generalised hypergeometric function, summed term by
12042/// term from the ratio between neighbours —
12043/// `t[k+1] = t[k] × (Π(m+k) ÷ Π(n+k)) × y ÷ (k+1)`.
12044///
12045/// A parameter on both sides contributes the same factor to each product,
12046/// so the pairs are cancelled first: that is what makes `0 H. 0` the
12047/// exponential rather than a term of `0÷0`.
12048fn hypergeometric(num: &[Cx], den: &[Cx], y: &Array, span: Span) -> Result<Array> {
12049    let (num, den) = cancel_parameters(num, den);
12050    let at = poly_coeffs(y, span)?;
12051    let mut out = Vec::with_capacity(at.len());
12052    for z in &at {
12053        out.push(hypergeometric_at(&num, &den, *z, span)?);
12054    }
12055    let mut a = complex_or_real(out);
12056    a.shape = y.shape.clone();
12057    Ok(a)
12058}
12059
12060/// The parameters left once every value common to both lists is dropped
12061/// from each, one occurrence at a time.
12062fn cancel_parameters(num: &[Cx], den: &[Cx]) -> (Vec<Cx>, Vec<Cx>) {
12063    let mut left: Vec<Cx> = Vec::with_capacity(num.len());
12064    let mut right: Vec<Cx> = den.to_vec();
12065    for a in num {
12066        match right.iter().position(|b| b == a) {
12067            Some(i) => {
12068                right.remove(i);
12069            }
12070            None => left.push(*a),
12071        }
12072    }
12073    (left, right)
12074}
12075
12076fn hypergeometric_at(num: &[Cx], den: &[Cx], z: Cx, span: Span) -> Result<Cx> {
12077    // Wholly real arguments are summed in real arithmetic, where dividing
12078    // by a zero parameter gives the infinity J answers with; the complex
12079    // quotient would make that same division a NaN in both parts.
12080    let real = |v: &[Cx]| v.iter().all(|c| c[1] == 0.0);
12081    if z[1] == 0.0 && real(num) && real(den) {
12082        let n: Vec<f64> = num.iter().map(|c| c[0]).collect();
12083        let d: Vec<f64> = den.iter().map(|c| c[0]).collect();
12084        return Ok([hypergeometric_real(&n, &d, z[0], span)?, 0.0]);
12085    }
12086    let mut sum = cx::ONE;
12087    let mut term = cx::ONE;
12088    for k in 0..HYPERGEOMETRIC_TERMS {
12089        let kk = [k as f64, 0.0];
12090        let mut ratio = z;
12091        for a in num {
12092            ratio = cx::mul(ratio, cx::add(*a, kk));
12093        }
12094        for b in den {
12095            ratio = cx::div(ratio, cx::add(*b, kk));
12096        }
12097        term = cx::div(cx::mul(term, ratio), [k as f64 + 1.0, 0.0]);
12098        if !term[0].is_finite() || !term[1].is_finite() {
12099            // A zero denominator parameter, or a term past the range of a
12100            // double: the sum is the infinity (or NaN) the term became.
12101            return Ok(term);
12102        }
12103        let before = sum;
12104        sum = cx::add(sum, term);
12105        // The series has converged once a term no longer moves the sum.
12106        if sum == before {
12107            return Ok(sum);
12108        }
12109    }
12110    Err(Error::domain(
12111        format!("the hypergeometric series did not converge within {HYPERGEOMETRIC_TERMS} terms"),
12112        span,
12113    ))
12114}
12115
12116fn hypergeometric_real(num: &[f64], den: &[f64], z: f64, span: Span) -> Result<f64> {
12117    let mut sum = 1.0f64;
12118    let mut term = 1.0f64;
12119    for k in 0..HYPERGEOMETRIC_TERMS {
12120        let kk = k as f64;
12121        let mut ratio = z;
12122        for a in num {
12123            ratio *= a + kk;
12124        }
12125        for b in den {
12126            ratio /= b + kk;
12127        }
12128        term = term * ratio / (kk + 1.0);
12129        if !term.is_finite() {
12130            return Ok(term);
12131        }
12132        let before = sum;
12133        sum += term;
12134        if sum == before {
12135            return Ok(sum);
12136        }
12137    }
12138    Err(Error::domain(
12139        format!("the hypergeometric series did not converge within {HYPERGEOMETRIC_TERMS} terms"),
12140        span,
12141    ))
12142}
12143
12144/// A complex vector as an array, real where every imaginary part is zero.
12145fn complex_or_real(values: Vec<Cx>) -> Array {
12146    if values.iter().all(|z| z[1] == 0.0) {
12147        return Array::from_f64(values.iter().map(|z| z[0]).collect());
12148    }
12149    Array::new(vec![values.len()], Data::Complex(values.into()))
12150}
12151
12152/// `x p. y`: the polynomial with ascending coefficients x, at y — Horner's
12153/// rule, or the product over the roots when x is the boxed root form.
12154fn poly_eval(x: &Array, y: &Array, span: Span) -> Result<Array> {
12155    let at = poly_coeffs(y, span)?;
12156    let at = at.first().copied().unwrap_or(cx::ZERO);
12157    let value = match x.as_boxes() {
12158        Some(parts) => {
12159            let (mut v, roots) = root_form(parts, span)?;
12160            for r in poly_coeffs_relaxed(roots, span)? {
12161                v = cx::mul(v, cx::sub(at, r));
12162            }
12163            v
12164        }
12165        None => {
12166            let c = poly_coeffs_relaxed(x, span)?;
12167            let mut v = cx::ZERO;
12168            for &k in c.iter().rev() {
12169                v = cx::add(cx::mul(v, at), k);
12170            }
12171            v
12172        }
12173    };
12174    Ok(scalar_complex_or_real(value))
12175}
12176
12177fn scalar_complex_or_real(z: Cx) -> Array {
12178    if z[1] == 0.0 {
12179        return Array::scalar_f64(z[0]);
12180    }
12181    Array::new(vec![], Data::Complex(vec![z].into()))
12182}
12183
12184/// `p. y`: the roots of the polynomial whose ascending coefficients y holds,
12185/// as `multiplier ; roots`; a y already in that form converts back to
12186/// coefficients.
12187fn poly_roots(y: &Array, span: Span) -> Result<Array> {
12188    if let Some(parts) = y.as_boxes().filter(|p| !p.is_empty()) {
12189        return Ok(complex_or_real(root_form_coeffs(parts, span)?));
12190    }
12191    let mut c = poly_coeffs_relaxed(y, span)?;
12192    while c.len() > 1 && c[c.len() - 1] == cx::ZERO {
12193        c.pop();
12194    }
12195    // The ZERO polynomial has no leading coefficient to divide by and every
12196    // number for a root: J answers `0 ; ''`, a zero multiplier and no roots
12197    // at all. Only a non-zero constant has no root form.
12198    if c.iter().all(|&k| k == cx::ZERO) {
12199        let pair = vec![Array::scalar_i64(0), Array::new(vec![0], Data::empty(DType::I64))];
12200        return Ok(Array::new(vec![2], Data::Box(pair.into())));
12201    }
12202    if c.len() < 2 {
12203        return Err(Error::domain("a polynomial's roots need a coefficient of x", span));
12204    }
12205    let lead = c[c.len() - 1];
12206    let monic: Vec<Cx> = c.iter().map(|&k| cx::div(k, lead)).collect();
12207    let roots = durand_kerner(&monic);
12208    let pair = vec![scalar_complex_or_real(lead), complex_or_real(roots)];
12209    Ok(Array::new(vec![2], Data::Box(pair.into())))
12210}
12211
12212/// The roots of a monic polynomial, by the Durand–Kerner iteration: every
12213/// root is refined against all the others at once, from spread-out starting
12214/// points, until none of them moves.
12215///
12216/// The answer is ordered by descending real part, then descending
12217/// imaginary part, which is a stable order the iteration itself has none of.
12218fn durand_kerner(monic: &[Cx]) -> Vec<Cx> {
12219    let d = monic.len() - 1;
12220    let seed = [0.4, 0.9];
12221    let mut z: Vec<Cx> = Vec::with_capacity(d);
12222    let mut p = cx::ONE;
12223    for _ in 0..d {
12224        z.push(p);
12225        p = cx::mul(p, seed);
12226    }
12227    let value = |monic: &[Cx], at: Cx| {
12228        let mut v = cx::ZERO;
12229        for &k in monic.iter().rev() {
12230            v = cx::add(cx::mul(v, at), k);
12231        }
12232        v
12233    };
12234    for _ in 0..500 {
12235        let mut moved: f64 = 0.0;
12236        for i in 0..d {
12237            let mut denom = cx::ONE;
12238            for j in 0..d {
12239                if i != j {
12240                    denom = cx::mul(denom, cx::sub(z[i], z[j]));
12241                }
12242            }
12243            if denom == cx::ZERO {
12244                continue;
12245            }
12246            let step = cx::div(value(monic, z[i]), denom);
12247            z[i] = cx::sub(z[i], step);
12248                moved = moved.max(step[0].hypot(step[1]));
12249        }
12250        if moved < 1e-15 {
12251            break;
12252        }
12253    }
12254    let mut z = polished_repeats(monic, z);
12255    // A root within rounding of the real axis is a real root.
12256    for r in &mut z {
12257        if r[1].abs() < 1e-9 {
12258            r[1] = 0.0;
12259        }
12260        if r[0].abs() < 1e-12 {
12261            r[0] = 0.0;
12262        }
12263    }
12264    // Order is the one J answers in: the largest magnitude first, then the
12265    // largest real part, then the largest imaginary part — so ¯3 comes
12266    // before 2, and a conjugate pair keeps the positive half in front. The
12267    // keys are coarsened first, because two members of a pair agree only
12268    // to rounding and the sort needs a total order to stand on.
12269    let coarse = |v: f64| -> f64 {
12270        if v == 0.0 || !v.is_finite() { v } else { format!("{v:.11e}").parse().unwrap_or(v) }
12271    };
12272    let mut keyed: Vec<([f64; 3], Cx)> =
12273        z.into_iter().map(|r| ([coarse(cx::abs(r)), coarse(r[0]), coarse(r[1])], r)).collect();
12274    keyed.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
12275    keyed.into_iter().map(|(_, r)| r).collect()
12276}
12277
12278/// A repeated root, put back where it belongs.
12279///
12280/// Durand–Kerner reaches a root of multiplicity m only to about the m-th
12281/// root of the machine epsilon, so a double root of `1 2 1` comes out as
12282/// two complex values 1e¯8 either side of ¯1: complex noise where the
12283/// answer is a pair of exact reals. The straddle is symmetric, so the
12284/// group's CENTRE carries the accuracy its members lack. Roots within reach
12285/// of one another are gathered and every member of a group moves to the
12286/// group's centre.
12287///
12288/// Reach is a guess, and a wrong one merges two roots that are merely
12289/// close. So the answer is kept only when the polynomial rebuilt from it
12290/// fits the coefficients at least as well as the raw roots do, and the
12291/// widest reach that passes that test is the one taken.
12292fn polished_repeats(monic: &[Cx], z: Vec<Cx>) -> Vec<Cx> {
12293    let d = z.len();
12294    if d < 2 {
12295        return z;
12296    }
12297    let raw = coefficient_error(monic, &z);
12298    let scale = monic.iter().map(|&k| cx::abs(k)).fold(1.0f64, f64::max);
12299    let allowed = raw.max(1e-13 * scale);
12300    for reach in [1e-3, 1e-4, 1e-5, 1e-6, 1e-7] {
12301        // Single linkage: a chain of near neighbours is one group, which
12302        // is what a triple root's three points around the true value are.
12303        let mut group: Vec<usize> = (0..d).collect();
12304        for i in 0..d {
12305            for j in 0..i {
12306                let apart = cx::abs(cx::sub(z[i], z[j]));
12307                let span = reach * (1.0 + cx::abs(z[i]).max(cx::abs(z[j])));
12308                if apart <= span {
12309                    let (a, b) = (group[i], group[j]);
12310                    let (keep, drop) = (a.min(b), a.max(b));
12311                    for g in &mut group {
12312                        if *g == drop {
12313                            *g = keep;
12314                        }
12315                    }
12316                }
12317            }
12318        }
12319        let mut centre = vec![cx::ZERO; d];
12320        let mut size = vec![0usize; d];
12321        for i in 0..d {
12322            centre[group[i]] = cx::add(centre[group[i]], z[i]);
12323            size[group[i]] += 1;
12324        }
12325        if size.iter().all(|&n| n < 2) {
12326            return z;
12327        }
12328        let mut settled: Vec<Option<Cx>> = vec![None; d];
12329        for g in 0..d {
12330            if size[g] == 0 {
12331                continue;
12332            }
12333            let start = cx::div(centre[g], cx::from_real(size[g] as f64));
12334            // Near a root of multiplicity m the polynomial's own value is
12335            // lost to cancellation — it reads as zero over a whole ball —
12336            // so refining against it can go no further. The m-1st
12337            // DERIVATIVE has the same root simply, with none of that
12338            // cancellation, and Newton on it lands exactly: `1 3 3 1`'s
12339            // second derivative is `6 6`, whose one root is ¯1.
12340            settled[g] = Some(if size[g] < 2 {
12341                start
12342            } else {
12343                newton_at(&nth_derivative(monic, size[g] - 1), start)
12344            });
12345        }
12346        let out: Vec<Cx> = (0..d).map(|i| settled[group[i]].unwrap_or(z[i])).collect();
12347        if out.iter().all(|r| r[0].is_finite() && r[1].is_finite())
12348            && coefficient_error(monic, &out) <= allowed
12349        {
12350            return out;
12351        }
12352    }
12353    z
12354}
12355
12356/// Newton's method from `start`, on the coefficients as given.
12357fn newton_at(poly: &[Cx], start: Cx) -> Cx {
12358    let mut z = start;
12359    for _ in 0..40 {
12360        let (mut p, mut slope) = (cx::ZERO, cx::ZERO);
12361        for &k in poly.iter().rev() {
12362            slope = cx::add(cx::mul(slope, z), p);
12363            p = cx::add(cx::mul(p, z), k);
12364        }
12365        if slope == cx::ZERO {
12366            break;
12367        }
12368        let step = cx::div(p, slope);
12369        let next = cx::sub(z, step);
12370        if !next[0].is_finite() || !next[1].is_finite() {
12371            break;
12372        }
12373        z = next;
12374        if cx::abs(step) <= 1e-17 * (1.0 + cx::abs(z)) {
12375            break;
12376        }
12377    }
12378    z
12379}
12380
12381/// The `k`-th derivative of a polynomial's ascending coefficients.
12382fn nth_derivative(c: &[Cx], k: usize) -> Vec<Cx> {
12383    let mut out = c.to_vec();
12384    for _ in 0..k {
12385        if out.len() < 2 {
12386            return vec![cx::ZERO];
12387        }
12388        out = out
12389            .iter()
12390            .enumerate()
12391            .skip(1)
12392            .map(|(i, &v)| cx::mul(v, cx::from_real(i as f64)))
12393            .collect();
12394    }
12395    out
12396}
12397
12398/// How far the monic polynomial rebuilt from `roots` sits from the one the
12399/// coefficients describe: the largest coefficient difference, relative to
12400/// the coefficient it belongs to.
12401fn coefficient_error(monic: &[Cx], roots: &[Cx]) -> f64 {
12402    let mut built = vec![cx::ONE];
12403    for &r in roots {
12404        let mut next = vec![cx::ZERO; built.len() + 1];
12405        for (k, &c) in built.iter().enumerate() {
12406            next[k + 1] = cx::add(next[k + 1], c);
12407            next[k] = cx::sub(next[k], cx::mul(c, r));
12408        }
12409        built = next;
12410    }
12411    let mut worst: f64 = 0.0;
12412    for (k, &want) in monic.iter().enumerate() {
12413        let got = built.get(k).copied().unwrap_or(cx::ZERO);
12414        worst = worst.max(cx::abs(cx::sub(got, want)) / (1.0 + cx::abs(want)));
12415    }
12416    worst
12417}
12418
12419/// `p.. y`: the derivative of the polynomial y's ascending coefficients
12420/// describe, again as coefficients.
12421fn poly_deriv(y: &Array, span: Span) -> Result<Array> {
12422    // A boxed argument is the root form, differentiated through the
12423    // coefficients it stands for: `p.. (<1 2 3)` is `11 _12 3`.
12424    let c = match y.as_boxes().filter(|p| !p.is_empty()) {
12425        Some(parts) => root_form_coeffs(parts, span)?,
12426        None => poly_coeffs_relaxed(y, span)?,
12427    };
12428    if c.len() < 2 {
12429        return Ok(Array::from_i64(vec![0]));
12430    }
12431    let out: Vec<Cx> =
12432        c.iter().enumerate().skip(1).map(|(k, &v)| cx::mul(v, cx::from_real(k as f64))).collect();
12433    Ok(narrow_numbers(complex_or_real(out)))
12434}
12435
12436/// `x p.. y`: the integral of y's coefficients, with x as the constant term.
12437fn poly_integral(x: &Array, y: &Array, span: Span) -> Result<Array> {
12438    // A boxed argument is the root form here too. What it does NOT take is
12439    // an empty of another type: `1 p.. (0$'a')` is a domain error where
12440    // `p.. (0$'a')` answers, and the oracle's line is the line.
12441    let c = match y.as_boxes().filter(|p| !p.is_empty()) {
12442        Some(parts) => root_form_coeffs(parts, span)?,
12443        None => poly_coeffs(y, span)?,
12444    };
12445    let k = poly_coeffs(x, span)?;
12446    let mut out = vec![k.first().copied().unwrap_or(cx::ZERO)];
12447    for (i, &v) in c.iter().enumerate() {
12448        out.push(cx::div(v, cx::from_real((i + 1) as f64)));
12449    }
12450    Ok(narrow_numbers(complex_or_real(out)))
12451}
12452
12453/// A float array whose values are all whole, as integers. Polynomial
12454/// coefficients are computed in floats and mostly come out whole; J prints
12455/// and types them as integers, so libjay narrows them back.
12456fn narrow_numbers(a: Array) -> Array {
12457    let Data::F64(v) = &a.data else { return a };
12458    if v.iter().any(|x| !x.is_finite() || x.fract() != 0.0 || x.abs() > 9e15) {
12459        return a;
12460    }
12461    let values: Vec<i64> = v.iter().map(|&x| x as i64).collect();
12462    Array::new(a.shape, Data::I64(values.into()))
12463}
12464
12465/// `u b. n`: what u is, rather than what it does. Only `0`, the three
12466/// ranks, is answered; the rest of J's characteristics reach into the
12467/// representation of a verb, which libjay does not publish.
12468fn characteristics(u: &Verb, y: &Array, span: Span) -> Result<Array> {
12469    let which = y.to_i64_vec().and_then(|v| v.first().copied());
12470    let chars = |s: String| Ok(Array::from_chars(s.chars().collect()));
12471    match which {
12472        Some(0) => {
12473            let ranks = u.ranks();
12474            Ok(Array::from_f64(
12475                ranks
12476                    .iter()
12477                    .map(|&r| if r == RANK_INF { f64::INFINITY } else { r as f64 })
12478                    .collect(),
12479            ))
12480        }
12481        // `u b. _1` and `u b. 1` answer with a spelling, not a verb: the
12482        // obverse, and the verb that yields the identity element of a
12483        // reduction over no items.
12484        Some(-1) => match obverse(u) {
12485            Some(v) => chars(v.name()),
12486            None => Err(Error::not_yet(
12487                format!("the obverse of {} (no inverse is known)", u.name()),
12488                span,
12489            )),
12490        },
12491        // `b.` is J's conjunction and has no APL spelling, so the identity
12492        // asked for here is always J's.
12493        Some(1) => match reduce_identity(u, 1, crate::Lang::J).as_ref().map(identity_spelling) {
12494            Some(s) => chars(s),
12495            None => Err(Error::not_yet(
12496                format!("the identity function of {} (u b. 1)", u.name()),
12497                span,
12498            )),
12499        },
12500        _ => Err(Error::not_yet("a verb characteristic other than 0, 1 and _1", span)),
12501    }
12502}
12503
12504/// J spells an identity function as the neutral cell reshaped to the frame
12505/// of the argument: `+ b. 1` is `0 $~ }.@$`.
12506fn identity_spelling(d: &Data) -> String {
12507    let one = Array::new(Vec::new(), d.slice(0, 1));
12508    let text = crate::fmt::format_array(&one, &crate::fmt::FmtOpts::J);
12509    format!("{} $~ }}.@$", text.trim())
12510}
12511
12512/// Run `f` with `⍺⍺` and `⍵⍵` naming the operands a user-written operator
12513/// was given, and with whatever they named before put back afterwards.
12514///
12515/// An operand that is an array is bound as a NAME rather than as a verb,
12516/// which is how the body's `⍺⍺` reads as a value. Both slots are saved and
12517/// restored, so an operator applied inside another operator's body leaves
12518/// the outer names as it found them.
12519fn with_operands<R>(
12520    alpha: &Operand,
12521    omega: Option<&Operand>,
12522    ctx: &mut Ctx<'_>,
12523    f: impl FnOnce(&mut Ctx<'_>) -> Result<R>,
12524) -> Result<R> {
12525    let names = ["⍺⍺", "⍵⍵"];
12526    let operands = [Some(alpha), omega];
12527    let saved: Vec<(Option<Verb>, Option<Array>)> =
12528        names.iter().map(|n| (ctx.env.verb(n).cloned(), ctx.env.global(n))).collect();
12529    for (name, operand) in names.iter().zip(operands) {
12530        match operand {
12531            Some(Operand::Func(v)) => ctx.env.define((*name).to_string(), (**v).clone()),
12532            Some(Operand::Value(a)) => ctx.env.set_global((*name).to_string(), (**a).clone()),
12533            None => {}
12534        }
12535    }
12536    let out = f(ctx);
12537    for (name, (verb, value)) in names.iter().zip(saved) {
12538        match verb {
12539            Some(v) => ctx.env.define((*name).to_string(), v),
12540            None => ctx.env.undefine(name),
12541        }
12542        match value {
12543            Some(a) => ctx.env.set_global((*name).to_string(), a),
12544            None => ctx.env.unset_global(name),
12545        }
12546    }
12547    out
12548}
12549
12550/// True for APL's MIXED SIMPLE array: every element is a simple scalar,
12551/// and no one type holds all of them. libjay keeps such an array as boxed
12552/// scalars, but its depth is 1 and nothing may open it further.
12553fn is_mixed_simple(a: &Array) -> bool {
12554    let Some(items) = a.as_boxes() else { return false };
12555    if items.is_empty() || items.iter().any(|b| b.rank() != 0 || b.dtype() == DType::Box) {
12556        return false;
12557    }
12558    let mut common = Some(items[0].dtype());
12559    for b in &items[1..] {
12560        common = common.and_then(|t| DType::promote(t, b.dtype()));
12561    }
12562    common.is_none()
12563}
12564
12565/// APL `⊆ y` (Dyalog): nest — y enclosed, unless it already is nested or
12566/// is a simple scalar, neither of which enclosing changes.
12567fn nest(y: &Array) -> Array {
12568    if y.dtype() == DType::Box || y.rank() == 0 {
12569        return y.clone();
12570    }
12571    Array::boxed(y.clone())
12572}
12573
12574/// APL `f⌸ y` and `x f⌸ y` (Dyalog's key): the distinct major cells of the
12575/// left argument, in first-occurrence order, each paired with what shares
12576/// it — the positions it occupies, or the right argument's items there.
12577fn key_pairs(
12578    u: &Verb,
12579    keys: &Array,
12580    values: Option<&Array>,
12581    ctx: &mut Ctx<'_>,
12582    span: Span,
12583) -> Result<Array> {
12584    let base = if keys.rank() == 0 { Array::new(vec![1], keys.data.clone()) } else { keys.clone() };
12585    let n = base.items();
12586    if let Some(v) = values && v.items() != n {
12587        return Err(Error::new(
12588            ErrorKind::Length,
12589            format!("{n} key(s) for {} item(s)", v.items()),
12590            Some(span),
12591        ));
12592    }
12593    let groups = group_positions(&base, ctx.cfg.tol);
12594    let origin = ctx.cfg.rules.origin;
12595    let mut cells = Vec::with_capacity(groups.len());
12596    for (first, at) in &groups {
12597        let key = item_or_self(&base, *first);
12598        let group = match values {
12599            Some(v) => select_items(v, at),
12600            None => Array::from_i64(at.iter().map(|&i| origin + i as i64).collect()),
12601        };
12602        // A dfn that never names `⍺` has no dyadic valence; the key is
12603        // then of no use to it and the group is all it is given.
12604        let monadic = matches!(u, Verb::Explicit(d) if d.left.is_none());
12605        cells.push(if monadic {
12606            u.monad(&group, ctx, span)?
12607        } else {
12608            u.dyad(&key, &group, ctx, span)?
12609        });
12610    }
12611    let count = cells.len();
12612    assemble(&[count], cells, span)
12613}
12614
12615/// The distinct items of `y`, each as (its first position, every position
12616/// it holds), in first-occurrence order.
12617fn group_positions(y: &Array, tol: Tol) -> Vec<(usize, Vec<usize>)> {
12618    let n = y.items();
12619    let m = y.item_size();
12620    // Exact equality is an equivalence a hash stands in for, so the groups
12621    // come out of one pass. Tolerant equality is not one, and neither a box
12622    // nor an exact number has a cheap key: those are compared by content,
12623    // each item against the distinct ones already found.
12624    let hashable = match y.dtype() {
12625        DType::Box | DType::Ext | DType::Rat => false,
12626        DType::F64 | DType::Complex => tol.ct == 0.0,
12627        _ => true,
12628    };
12629    if hashable {
12630        return if m == 1 {
12631            group_by_key(n, |i| elem_key(&y.data, i))
12632        } else {
12633            group_by_key(n, |i| (0..m).map(|k| elem_key(&y.data, i * m + k)).collect::<Vec<u64>>())
12634        };
12635    }
12636    let mut keys: Vec<Array> = Vec::new();
12637    let mut groups: Vec<(usize, Vec<usize>)> = Vec::new();
12638    for i in 0..n {
12639        let item = y.item(i);
12640        match keys.iter().position(|k| arrays_match(k, &item, tol)) {
12641            Some(at) => groups[at].1.push(i),
12642            None => {
12643                keys.push(item);
12644                groups.push((i, vec![i]));
12645            }
12646        }
12647    }
12648    groups
12649}
12650
12651/// The positions `0 .. n`, grouped by the key each of them has, in the
12652/// order the keys first appear: one hash lookup per position, not one
12653/// comparison per position per group.
12654fn group_by_key<K, F>(n: usize, key: F) -> Vec<(usize, Vec<usize>)>
12655where
12656    K: Eq + std::hash::Hash,
12657    F: Fn(usize) -> K,
12658{
12659    use std::collections::hash_map::Entry;
12660    let mut groups: Vec<(usize, Vec<usize>)> = Vec::new();
12661    let mut at: HashMap<K, usize, KeyHash> =
12662        HashMap::with_capacity_and_hasher(n.min(1 << 16), KeyHash);
12663    for i in 0..n {
12664        match at.entry(key(i)) {
12665            Entry::Occupied(e) => groups[*e.get()].1.push(i),
12666            Entry::Vacant(e) => {
12667                e.insert(groups.len());
12668                groups.push((i, vec![i]));
12669            }
12670        }
12671    }
12672    groups
12673}
12674
12675/// The hasher the grouping uses. Its keys are [`elem_key`] values, which
12676/// already spread a value across the whole of a `u64`, so mixing them costs
12677/// a multiply where the default hasher runs a block cipher over them.
12678/// Nothing here is exposed to a chosen key, which is what that default is
12679/// for.
12680#[derive(Clone, Copy, Default)]
12681struct KeyHash;
12682
12683impl std::hash::BuildHasher for KeyHash {
12684    type Hasher = KeyHasher;
12685    fn build_hasher(&self) -> KeyHasher {
12686        KeyHasher(0)
12687    }
12688}
12689
12690struct KeyHasher(u64);
12691
12692impl std::hash::Hasher for KeyHasher {
12693    fn finish(&self) -> u64 {
12694        let mut x = self.0;
12695        x ^= x >> 33;
12696        x = x.wrapping_mul(0xff51_afd7_ed55_8ccd);
12697        x ^ (x >> 29)
12698    }
12699    fn write(&mut self, bytes: &[u8]) {
12700        for &b in bytes {
12701            self.write_u64(b as u64);
12702        }
12703    }
12704    fn write_u64(&mut self, n: u64) {
12705        self.0 = (self.0.rotate_left(5) ^ n).wrapping_mul(0x9e37_79b9_7f4a_7c15);
12706    }
12707    fn write_usize(&mut self, n: usize) {
12708        self.write_u64(n as u64);
12709    }
12710}
12711
12712/// APL `x ⍕ y`: format by specification. `x` is one width-and-precision
12713/// pair per column of y's last axis, one pair for all of them, or a lone
12714/// precision, which takes the width the values need plus a separating
12715/// blank. A value that does not fit its width is a domain error, as the
12716/// reference has it.
12717fn format_spec(x: &Array, y: &Array, fmt: &FmtOpts, span: Span) -> Result<Array> {
12718    let spec = x
12719        .to_i64_vec()
12720        .ok_or_else(|| Error::domain("a format specification is whole numbers", span))?;
12721    if y.dtype() == DType::Box {
12722        return Err(Error::not_yet("format by specification of a nested array", span));
12723    }
12724    let cols = if y.rank() == 0 { 1 } else { y.shape[y.rank() - 1] };
12725    let rows = y.count() / cols.max(1);
12726    // One number is a precision alone; pairs are width and precision.
12727    let pairs: Vec<(Option<i64>, i64)> = match spec.len() {
12728        1 => vec![(None, spec[0]); cols],
12729        2 => vec![(Some(spec[0]), spec[1]); cols],
12730        n if n == 2 * cols => spec.chunks(2).map(|c| (Some(c[0]), c[1])).collect(),
12731        n => {
12732            return Err(Error::new(
12733                ErrorKind::Length,
12734                format!("{n} specification value(s) for {cols} column(s)"),
12735                Some(span),
12736            ));
12737        }
12738    };
12739    if pairs.iter().any(|&(w, p)| w.is_some_and(|w| w < 0) || p < 0) {
12740        return Err(Error::domain("a format width and precision are nonnegative", span));
12741    }
12742    // A width and a precision are lengths, and a written number is free to
12743    // ask for more characters than any machine holds. The ceiling applies
12744    // here as it does to a shape.
12745    for &(w, p) in &pairs {
12746        crate::limits::count(w.unwrap_or(0) as u128, span)?;
12747        crate::limits::count(p as u128, span)?;
12748    }
12749    let numbers = y.to_f64_vec();
12750    let text = |i: usize, p: i64| -> String {
12751        match (&y.data, &numbers) {
12752            (Data::Char(v), _) => v[i].to_string(),
12753            (_, Some(v)) => {
12754                let s = format!("{:.*}", p as usize, v[i]);
12755                if v[i] < 0.0 { format!("{}{}", fmt.neg, &s[1..]) } else { s }
12756            }
12757            _ => String::new(),
12758        }
12759    };
12760    if y.dtype() != DType::Char && numbers.is_none() {
12761        return Err(Error::domain("format by specification takes numbers or characters", span));
12762    }
12763    // A width the caller did not give is the widest value plus a blank.
12764    let widths: Vec<usize> = pairs
12765        .iter()
12766        .enumerate()
12767        .map(|(c, &(w, p))| match w {
12768            Some(w) => w as usize,
12769            None => {
12770                (0..rows).map(|r| text(r * cols + c, p).chars().count()).max().unwrap_or(0) + 1
12771            }
12772        })
12773        .collect();
12774    let line = crate::limits::count(widths.iter().map(|&w| w as u128).sum(), span)?;
12775    let total = crate::limits::count(rows as u128 * line as u128, span)?;
12776    let mut out: Vec<char> = Vec::with_capacity(total);
12777    for r in 0..rows {
12778        for c in 0..cols {
12779            let s = text(r * cols + c, pairs[c].1);
12780            let len = s.chars().count();
12781            if len > widths[c] {
12782                return Err(Error::domain(
12783                    format!("{s} does not fit a field {} wide", widths[c]),
12784                    span,
12785                ));
12786            }
12787            out.extend(std::iter::repeat_n(' ', widths[c] - len));
12788            out.extend(s.chars());
12789        }
12790    }
12791    let mut shape = if y.rank() == 0 { Vec::new() } else { y.shape[..y.rank() - 1].to_vec() };
12792    shape.push(line);
12793    Ok(Array::new(shape, Data::Char(out.into())))
12794}
12795
12796/// J `x ;: y`: the sequential machine.
12797///
12798/// x is the boxed description `f ; s ; m ; ijrd`, of which `m` and `ijrd`
12799/// may be left off. `s` is the transition table, shaped `p q 2`: at state
12800/// `r` and input class `c`, `s[r;c;0]` is the state to go to and
12801/// `s[r;c;1]` the output code — 0 nothing, 1 start a word here, 2 end a
12802/// word and start another, 3 end a word, 6 stop. `m` maps an input element
12803/// to its class, indexed by the character's codepoint; with none, a
12804/// numeric argument IS the classes. `ijrd` is the starting position, the
12805/// starting word (`_1` for none), the starting state and what to do with
12806/// the end of the input: a class to make one last transition with, or `_1`
12807/// to end the word in hand. `f` picks the answer: 0 the boxed words, 1
12808/// their elements catenated, 2 each word's position and length, 3 the
12809/// table position that ended it, 4 both, 5 the whole trace.
12810fn sequential_machine(x: &Array, y: &Array, span: Span) -> Result<Array> {
12811    let Some(parts) = x.as_boxes() else {
12812        return Err(Error::domain("a sequential machine is a boxed description", span));
12813    };
12814    if x.rank() > 1 || !(2..=4).contains(&parts.len()) {
12815        return Err(Error::domain(
12816            "a sequential machine is 2 to 4 boxes: f ; s ; m ; ijrd",
12817            span,
12818        ));
12819    }
12820    let whole = |a: &Array, what: &str| -> Result<Vec<i64>> {
12821        a.to_i64_vec().ok_or_else(|| Error::domain(format!("{what} is whole numbers"), span))
12822    };
12823    let form = *whole(&parts[0], "a sequential machine's result form")?
12824        .first()
12825        .ok_or_else(|| Error::domain("a sequential machine needs a result form", span))?;
12826    if !(0..=5).contains(&form) {
12827        return Err(Error::domain(format!("{form} is not a result form of 0 to 5"), span));
12828    }
12829    let table = &parts[1];
12830    if table.rank() != 3 || table.shape[2] != 2 {
12831        return Err(Error::new(
12832            ErrorKind::Rank,
12833            "a sequential machine's transition table is shaped p q 2",
12834            Some(span),
12835        ));
12836    }
12837    let (states, classes) = (table.shape[0], table.shape[1]);
12838    let entries = whole(table, "a transition table")?;
12839    let map = parts.get(2).filter(|a| a.count() > 0);
12840    let start = match parts.get(3) {
12841        Some(a) => whole(a, "a sequential machine's starting values")?,
12842        None => Vec::new(),
12843    };
12844    let start = if start.is_empty() { vec![0, -1, 0, -1] } else { start };
12845    if start.len() != 4 {
12846        return Err(Error::new(
12847            ErrorKind::Length,
12848            "a sequential machine starts from four values: i j r d",
12849            Some(span),
12850        ));
12851    }
12852    let (mut i, mut word, mut state, ending) = (start[0], start[1], start[2], start[3]);
12853    let n = y.count() as i64;
12854
12855    // The class of the element at `at`: read through the map where there
12856    // is one, and the element itself where there is not.
12857    let codes: Option<Vec<i64>> = match map {
12858        Some(m) => Some(whole(m, "a sequential machine's map")?),
12859        None => None,
12860    };
12861    let values: Vec<i64> = match (&y.data, &codes) {
12862        (Data::Char(v), Some(_)) => v.as_slice().iter().map(|&c| c as i64).collect(),
12863        (_, None) => y
12864            .to_i64_vec()
12865            .ok_or_else(|| Error::domain("a sequential machine over characters needs a map", span))?,
12866        _ => {
12867            return Err(Error::not_yet(
12868                "a sequential machine's map over a numeric argument (x's third box)",
12869                span,
12870            ));
12871        }
12872    };
12873    let class_at = |at: i64| -> Result<i64> {
12874        let raw = values[at as usize];
12875        let Some(m) = &codes else { return Ok(raw) };
12876        if raw < 0 || raw as usize >= m.len() {
12877            return Err(Error::new(
12878                ErrorKind::Domain,
12879                format!("{raw} is outside a map of {} entries", m.len()),
12880                Some(span),
12881            ));
12882        }
12883        Ok(m[raw as usize])
12884    };
12885
12886    let mut trace: Vec<i64> = Vec::new();
12887    let mut words: Vec<(i64, i64, i64)> = Vec::new();
12888    let mut emit = |word: i64, at: i64, place: i64| -> Result<()> {
12889        if word < 0 {
12890            return Err(Error::new(
12891                ErrorKind::Domain,
12892                "a sequential machine ended a word before one had begun",
12893                Some(span),
12894            ));
12895        }
12896        words.push((word, at - word, place));
12897        Ok(())
12898    };
12899    loop {
12900        let class = if i < n {
12901            class_at(i)?
12902        } else if ending >= 0 {
12903            ending
12904        } else {
12905            // The input is spent and the end asks for no transition: what
12906            // is in hand is the last word. The reference gives it the table
12907            // position class 0 in the state reached would have.
12908            if word >= 0 {
12909                emit(word, i, classes as i64 * state)?;
12910            }
12911            break;
12912        };
12913        if state < 0 || state as usize >= states || class < 0 || class as usize >= classes {
12914            return Err(Error::new(
12915                ErrorKind::Domain,
12916                format!(
12917                    "state {state} and class {class} are outside a {states} by {classes} table"
12918                ),
12919                Some(span),
12920            ));
12921        }
12922        let at = (state as usize * classes + class as usize) * 2;
12923        let (next, code) = (entries[at], entries[at + 1]);
12924        trace.extend_from_slice(&[i, word, state, class, next, code]);
12925        let place = class + classes as i64 * state;
12926        state = next;
12927        match code {
12928            0 => {}
12929            1 => word = i,
12930            2 => {
12931                emit(word, i, place)?;
12932                word = i;
12933            }
12934            3 => {
12935                emit(word, i, place)?;
12936                word = -1;
12937            }
12938            4 | 5 => {
12939                return Err(Error::not_yet(
12940                    "a sequential machine's vector output (codes 4 and 5)",
12941                    span,
12942                ));
12943            }
12944            6 => break,
12945            other => {
12946                return Err(Error::domain(
12947                    format!("{other} is not a sequential machine output code"),
12948                    span,
12949                ));
12950            }
12951        }
12952        if i >= n {
12953            break;
12954        }
12955        i += 1;
12956    }
12957    Ok(sequential_result(form, &words, &trace, y))
12958}
12959
12960/// The answer a sequential machine's result form asks for, out of the words
12961/// it marked off and the trace it left.
12962fn sequential_result(form: i64, words: &[(i64, i64, i64)], trace: &[i64], y: &Array) -> Array {
12963    let piece = |&(at, len, _): &(i64, i64, i64)| {
12964        Array::new(vec![len as usize], y.data.slice(at as usize, (at + len) as usize))
12965    };
12966    match form {
12967        0 => Array::new(
12968            vec![words.len()],
12969            Data::Box(words.iter().map(piece).collect::<Vec<_>>().into()),
12970        ),
12971        1 => {
12972            let mut data = Data::empty(y.dtype());
12973            for w in words {
12974                data.extend_from(&piece(w).data);
12975            }
12976            let n = data.len();
12977            Array::new(vec![n], data)
12978        }
12979        2 => Array::new(
12980            vec![words.len(), 2],
12981            Data::I64(words.iter().flat_map(|&(at, len, _)| [at, len]).collect::<Vec<_>>().into()),
12982        ),
12983        3 => Array::from_i64(words.iter().map(|&(_, _, place)| place).collect()),
12984        4 => Array::new(
12985            vec![words.len(), 3],
12986            Data::I64(
12987                words
12988                    .iter()
12989                    .flat_map(|&(at, len, place)| [at, len, place])
12990                    .collect::<Vec<_>>()
12991                    .into(),
12992            ),
12993        ),
12994        _ => Array::new(vec![trace.len() / 6, 6], Data::I64(trace.to_vec().into())),
12995    }
12996}
12997
12998/// J `x ". y`: the numbers the characters of y spell, with x standing in
12999/// for every blank-separated word that is not a number. y arrives as one
13000/// line — the verb's right rank is 1 — so a character matrix is read a row
13001/// at a time and the rows are framed back together.
13002fn parse_numbers(x: &Array, y: &Array, span: Span) -> Result<Array> {
13003    if x.count() != 1 {
13004        return Err(Error::new(
13005            ErrorKind::Rank,
13006            "the stand-in for an unreadable word is one value",
13007            Some(span),
13008        ));
13009    }
13010    // No character to read is no word to read it as, whatever type the
13011    // empty right argument was going to hold: `0.5 ". i.0` is the empty.
13012    if y.count() == 0 {
13013        return Ok(Array::new(y.shape.clone(), Data::empty(DType::Bool)));
13014    }
13015    let Data::Char(text) = &y.data else {
13016        return Err(Error::domain("reading numbers from text needs characters", span));
13017    };
13018    let line: String = text.as_slice().iter().collect();
13019    crate::frontend::j::numbers_from_text(&line, x)
13020        .ok_or_else(|| Error::domain("the stand-in for an unreadable word is a number", span))
13021}
13022
13023/// One field of J's `x ": y`, without its padding: `w j d` says how wide
13024/// the field is and how many digits follow the point, and a NEGATIVE width
13025/// asks for the exponential form instead of the fixed one.
13026fn format_field(value: f64, precision: usize, exponential: bool, neg: char) -> String {
13027    let sign = |s: String| match s.strip_prefix('-') {
13028        // A value that rounds to nothing keeps no sign, as the reference
13029        // has it: `5j2 ": _0.001` is ` 0.00`.
13030        Some(rest) if rest.bytes().all(|b| !b.is_ascii_digit() || b == b'0') => rest.to_string(),
13031        Some(rest) => format!("{neg}{rest}"),
13032        None => s,
13033    };
13034    if !exponential {
13035        return sign(format!("{value:.precision$}"));
13036    }
13037    // `1.500e3`, `1.234e_4`: the mantissa to the asked-for precision, then
13038    // the exponent written as J writes an integer.
13039    let text = format!("{value:.precision$e}");
13040    let (mantissa, exponent) = text.split_once('e').unwrap_or((text.as_str(), "0"));
13041    let exponent = match exponent.strip_prefix('-') {
13042        Some(rest) => format!("{neg}{rest}"),
13043        None => exponent.to_string(),
13044    };
13045    format!("{}e{exponent}", sign(mantissa.to_string()))
13046}
13047
13048/// J `x ": y`: format by specification.
13049///
13050/// x is one complex `w j d` per column of y's last axis, or one for all of
13051/// them: `w` is the field width and `d` the digits after the point. A width
13052/// of zero takes whatever the column needs, with a blank between it and the
13053/// column before. A value too wide for its field is written as asterisks
13054/// rather than refused, which is what the reference does.
13055fn format_spec_j(x: &Array, y: &Array, fmt: &FmtOpts, span: Span) -> Result<Array> {
13056    let Some(spec) = x.to_complex_vec() else {
13057        return Err(Error::domain("a format specification is numbers", span));
13058    };
13059    if y.dtype() == DType::Box {
13060        return Err(Error::domain("format by specification takes numbers", span));
13061    }
13062    let Some(values) = y.to_f64_vec() else {
13063        return Err(Error::domain("format by specification takes numbers", span));
13064    };
13065    let cols = if y.rank() == 0 { 1 } else { y.shape[y.rank() - 1] };
13066    let rows = if cols == 0 { 0 } else { y.count() / cols };
13067    let fields: Vec<[f64; 2]> = match spec.len() {
13068        1 => vec![spec[0]; cols],
13069        n if n == cols => spec,
13070        n => {
13071            return Err(Error::new(
13072                ErrorKind::Length,
13073                format!("{n} specification value(s) for {cols} column(s)"),
13074                Some(span),
13075            ));
13076        }
13077    };
13078    // A width and a digit count are lengths, and a written number is free
13079    // to ask for more characters than any machine holds. The ceiling
13080    // applies here as it does to a shape: refuse the request instead of
13081    // handing the product to an allocator.
13082    for &[w, d] in &fields {
13083        crate::limits::count(w.abs() as u128, span)?;
13084        crate::limits::count(d.max(0.0) as u128, span)?;
13085    }
13086    let text = |r: usize, c: usize| {
13087        let [w, d] = fields[c];
13088        // Only a column of automatic width renders every digit asked for.
13089        // Where the width is given, a digit count that reaches it already
13090        // overflows the field — the point and the digits alone are wider —
13091        // so rendering past that point cannot change the answer.
13092        let digits = if w == 0.0 { d.max(0.0) } else { d.max(0.0).min(w.abs()) };
13093        format_field(values[r * cols + c], digits as usize, w < 0.0, fmt.neg)
13094    };
13095    // A width of zero is the widest value in the column, and a blank
13096    // between it and whatever stands to its left.
13097    let widths: Vec<usize> = (0..cols)
13098        .map(|c| {
13099            let w = fields[c][0];
13100            if w != 0.0 {
13101                return w.abs() as usize;
13102            }
13103            let wide = (0..rows).map(|r| text(r, c).chars().count()).max().unwrap_or(0);
13104            wide + usize::from(c > 0)
13105        })
13106        .collect();
13107    let line = crate::limits::count(widths.iter().map(|&w| w as u128).sum(), span)?;
13108    let total = crate::limits::count(rows as u128 * line as u128, span)?;
13109    let mut out: Vec<char> = Vec::with_capacity(total);
13110    for r in 0..rows {
13111        for c in 0..cols {
13112            let s = text(r, c);
13113            // The exponential form is written from the LEFT, one column of
13114            // sign in front of it; the fixed one is right-justified.
13115            let (lead, body) = match (fields[c][0] < 0.0, s.strip_prefix(fmt.neg)) {
13116                (false, _) => (String::new(), s.as_str()),
13117                (true, Some(rest)) => (fmt.neg.to_string(), rest),
13118                (true, None) => (" ".to_string(), s.as_str()),
13119            };
13120            let len = lead.chars().count() + body.chars().count();
13121            if len > widths[c] {
13122                out.extend(std::iter::repeat_n('*', widths[c]));
13123                continue;
13124            }
13125            if fields[c][0] < 0.0 {
13126                out.extend(lead.chars());
13127                out.extend(body.chars());
13128                out.extend(std::iter::repeat_n(' ', widths[c] - len));
13129            } else {
13130                out.extend(std::iter::repeat_n(' ', widths[c] - len));
13131                out.extend(body.chars());
13132            }
13133        }
13134    }
13135    let mut shape = if y.rank() == 0 { Vec::new() } else { y.shape[..y.rank() - 1].to_vec() };
13136    shape.push(line);
13137    Ok(Array::new(shape, Data::Char(out.into())))
13138}
13139
13140/// APL `⍳ y`: the indices of an array whose shape is y. One length gives
13141/// the plain counting vector; two or more give an array of that shape whose
13142/// elements are the boxed coordinate vectors.
13143fn iota_apl(y: &Array, origin: i64, near: NearInt, span: Span) -> Result<Array> {
13144    if y.rank() > 1 {
13145        return Err(Error::new(
13146            ErrorKind::Rank,
13147            "the index generator takes a shape, which is a scalar or a vector",
13148            Some(span),
13149        ));
13150    }
13151    let dims = y
13152        .to_i64_vec_near(near)
13153        .ok_or_else(|| Error::domain("index generator needs an integer argument", span))?;
13154    if dims.iter().any(|&n| n < 0) {
13155        return Err(Error::domain("index generator needs nonnegative lengths", span));
13156    }
13157    if dims.len() <= 1 {
13158        let n = dims.first().copied().unwrap_or(0);
13159        crate::limits::count(n as u128, span)?;
13160        return Ok(Array::from_i64((0..n).map(|i| origin + i).collect()));
13161    }
13162    let shape: Vec<usize> = dims.iter().map(|&n| n as usize).collect();
13163    let total = crate::limits::elements(&shape, span)?;
13164    let mut cells = Vec::with_capacity(total);
13165    let mut coord = vec![0usize; shape.len()];
13166    for _ in 0..total {
13167        cells.push(Array::from_i64(coord.iter().map(|&c| origin + c as i64).collect()));
13168        odometer(&mut coord, &shape);
13169    }
13170    Ok(Array::new(shape, Data::Box(cells.into())))
13171}
13172
13173/// J carries an argument's exactness into the verbs that answer with
13174/// counts and digits: `$`, `#`, `#.`, `#:`, `p:` and `q:` of an extended or
13175/// rational argument answer with extended integers, not machine ones. The
13176/// values are the same either way; only the type differs, and J's own
13177/// `3!:0` reports it.
13178fn carry_exact(result: Array, y: &Array) -> Array {
13179    if !matches!(y.dtype(), DType::Ext | DType::Rat) {
13180        return result;
13181    }
13182    match result.data.cast(DType::Ext) {
13183        Some(data) => Array::new(result.shape, data),
13184        None => result,
13185    }
13186}
13187
13188fn carry_exact2(result: Array, x: &Array, y: &Array) -> Array {
13189    let widened = carry_exact(result, x);
13190    carry_exact(widened, y)
13191}
13192
13193/// `m b.`: one of the sixteen boolean functions of two bits, and — sixteen
13194/// higher — the same function applied to every bit of a pair of integers.
13195fn truth_table(m: u8, x: &Array, y: &Array, span: Span) -> Result<Array> {
13196    let table = m & 15;
13197    let bit = |a: i64, b: i64| ((table >> (3 - (2 * a + b))) & 1) as i64;
13198    let xs = x
13199        .to_i64_vec()
13200        .ok_or_else(|| Error::domain("a boolean function takes integers", span))?;
13201    let ys = y
13202        .to_i64_vec()
13203        .ok_or_else(|| Error::domain("a boolean function takes integers", span))?;
13204    let (a, b) = (xs.first().copied().unwrap_or(0), ys.first().copied().unwrap_or(0));
13205    if m < 16 {
13206        if !(0..=1).contains(&a) || !(0..=1).contains(&b) {
13207            return Err(Error::domain(
13208                format!("{m} b. takes 0 and 1; {m} b. + 16 is the same function on every bit"),
13209                span,
13210            ));
13211        }
13212        return Ok(Array::scalar_bool(bit(a, b) != 0));
13213    }
13214    let mut out = 0i64;
13215    for k in 0..64 {
13216        if bit((a >> k) & 1, (b >> k) & 1) != 0 {
13217            out |= 1i64 << k;
13218        }
13219    }
13220    Ok(Array::scalar_i64(out))
13221}
13222
13223/// APL `A[i;j]←v`: `base` with the elements the slots select replaced by
13224/// `value`. An elided slot takes its whole axis; a scalar slot drops its
13225/// axis from the shape the value has to match. The base is copied, so the
13226/// array the name held before is untouched.
13227pub fn amend_at(
13228    base: &Array,
13229    slots: &[Option<Array>],
13230    value: &Array,
13231    origin: i64,
13232    near: NearInt,
13233    span: Span,
13234) -> Result<Array> {
13235    if slots.len() != base.rank() {
13236        return Err(Error::new(
13237            ErrorKind::Rank,
13238            format!(
13239                "indexed assignment needs one index per axis: {} slot(s) for a rank-{} value",
13240                slots.len(),
13241                base.rank()
13242            ),
13243            Some(span),
13244        ));
13245    }
13246    // The positions below are row-major offsets into both buffers, so a
13247    // column-major one is laid out before it is read or written.
13248    if !base.is_row_major() || !value.is_row_major() {
13249        let (b, v) = (base.to_row_major(), value.to_row_major());
13250        return amend_at(&b, slots, &v, origin, near, span);
13251    }
13252    // One list of positions per axis, and the shape the value must match.
13253    let mut axes: Vec<Vec<usize>> = Vec::with_capacity(slots.len());
13254    let mut selected: Vec<usize> = Vec::new();
13255    for (k, slot) in slots.iter().enumerate() {
13256        let len = base.shape[k];
13257        let Some(idx) = slot else {
13258            axes.push((0..len).collect());
13259            selected.push(len);
13260            continue;
13261        };
13262        let Some(values) = idx.to_i64_vec_near(near) else {
13263            return Err(Error::new(
13264                ErrorKind::Type,
13265                "an index must be numeric",
13266                Some(span),
13267            ));
13268        };
13269        let mut positions = Vec::with_capacity(values.len());
13270        for v in values {
13271            let p = v - origin;
13272            if p < 0 || p as usize >= len {
13273                return Err(Error::new(
13274                    ErrorKind::Domain,
13275                    format!("index {v} is outside axis {k}, which has {len} element(s)"),
13276                    Some(span),
13277                ));
13278            }
13279            positions.push(p as usize);
13280        }
13281        // A scalar index drops its axis, as it does when reading.
13282        if idx.rank() > 0 {
13283            selected.push(positions.len());
13284        }
13285        axes.push(positions);
13286    }
13287    let count: usize = axes.iter().map(Vec::len).product();
13288    if value.rank() != 0 && (value.shape != selected || value.count() != count) {
13289        return Err(Error::new(
13290            ErrorKind::Shape,
13291            format!(
13292                "indexed assignment needs a scalar or a {} value, not a {} one",
13293                show_shape(&selected),
13294                show_shape(&value.shape)
13295            ),
13296            Some(span),
13297        ));
13298    }
13299    // The two sides meet at the wider type, so assigning a float into an
13300    // integer array widens the array rather than truncating the value.
13301    let dtype = DType::promote(base.dtype(), value.dtype()).ok_or_else(|| {
13302        Error::new(
13303            ErrorKind::Type,
13304            format!(
13305                "cannot put a {} value into a {} array",
13306                value.dtype().name(),
13307                base.dtype().name()
13308            ),
13309            Some(span),
13310        )
13311    })?;
13312    let mut out = base.cast(dtype).ok_or_else(|| Error::internal("promotion failed"))?;
13313    let src = value.cast(dtype).ok_or_else(|| Error::internal("promotion failed"))?;
13314    let strides = row_major_strides(&base.shape);
13315    let mut coords = vec![0usize; axes.len()];
13316    for n in 0..count {
13317        let mut rest = n;
13318        for k in (0..axes.len()).rev() {
13319            let len = axes[k].len();
13320            coords[k] = axes[k][rest % len];
13321            rest /= len;
13322        }
13323        let at: usize = coords.iter().zip(&strides).map(|(c, s)| c * s).sum();
13324        let from = if src.rank() == 0 { 0 } else { n };
13325        put_element(&mut out.data, at, &src.data, from);
13326    }
13327    Ok(out)
13328}
13329
13330fn row_major_strides(shape: &[usize]) -> Vec<usize> {
13331    let mut strides = vec![1usize; shape.len()];
13332    for k in (0..shape.len().saturating_sub(1)).rev() {
13333        strides[k] = strides[k + 1] * shape[k + 1];
13334    }
13335    strides
13336}
13337
13338/// Copy one element between two buffers of the same type.
13339fn put_element(dst: &mut Data, at: usize, src: &Data, from: usize) {
13340    match (dst, src) {
13341        (Data::Bool(d), Data::Bool(s)) => d.to_mut()[at] = s.as_slice()[from],
13342        (Data::I64(d), Data::I64(s)) => d.to_mut()[at] = s.as_slice()[from],
13343        (Data::Ext(d), Data::Ext(s)) => d.to_mut()[at] = s.as_slice()[from].clone(),
13344        (Data::Rat(d), Data::Rat(s)) => d.to_mut()[at] = s.as_slice()[from].clone(),
13345        (Data::F64(d), Data::F64(s)) => d.to_mut()[at] = s.as_slice()[from],
13346        (Data::Char(d), Data::Char(s)) => d.to_mut()[at] = s.as_slice()[from],
13347        (Data::Box(d), Data::Box(s)) => d.to_mut()[at] = s.as_slice()[from].clone(),
13348        // Both sides were cast to one type above.
13349        _ => debug_assert!(false, "amend across types"),
13350    }
13351}
13352
13353/// Which of an agenda's verbs the selector picks. The selector runs at the
13354/// same arguments the agenda was given, and its value must be one index.
13355fn agenda_pick(
13356    vs: &[Verb],
13357    w: &Verb,
13358    x: Option<&Array>,
13359    y: &Array,
13360    ctx: &mut Ctx<'_>,
13361    span: Span,
13362) -> Result<Verb> {
13363    let chosen = match x {
13364        None => w.monad(y, ctx, span)?,
13365        Some(x) => w.dyad(x, y, ctx, span)?,
13366    };
13367    let at = chosen
13368        .to_i64_vec_near(ctx.cfg.near())
13369        .and_then(|v| v.first().copied())
13370        .ok_or_else(|| Error::domain("an agenda index must be an integer", span))?;
13371    pick_gerund(vs, at, span)
13372}
13373
13374/// One verb of a gerund by index, with the diagnostic the out-of-range case
13375/// deserves.
13376pub(crate) fn pick_gerund(vs: &[Verb], at: i64, span: Span) -> Result<Verb> {
13377    usize::try_from(at)
13378        .ok()
13379        .and_then(|k| vs.get(k))
13380        .cloned()
13381        .ok_or_else(|| {
13382            Error::domain(
13383                format!("agenda {at} is out of range: the gerund has {} verbs", vs.len()),
13384                span,
13385            )
13386        })
13387}
13388
13389/// `` m`:0 `` and `` m`:3 ``, the two evoke-gerund forms that are not a
13390/// train. `0` applies every verb of the gerund to the arguments and frames
13391/// the answers; `3` inserts the verbs between the items of y, taking them
13392/// left to right and cycling, and folds right to left as insert does.
13393fn evoke(
13394    vs: &[Verb],
13395    form: i64,
13396    x: Option<&Array>,
13397    y: &Array,
13398    ctx: &mut Ctx<'_>,
13399    span: Span,
13400) -> Result<Array> {
13401    if vs.is_empty() {
13402        return Err(Error::domain("an evoked gerund is empty", span));
13403    }
13404    if form == 0 {
13405        let mut cells = Vec::with_capacity(vs.len());
13406        for v in vs {
13407            cells.push(match x {
13408                None => v.monad(y, ctx, span)?,
13409                Some(x) => v.dyad(x, y, ctx, span)?,
13410            });
13411        }
13412        return assemble(&[vs.len()], cells, span);
13413    }
13414    if x.is_some() {
13415        return Err(Error::domain("m`:3 has no dyadic meaning", span));
13416    }
13417    let items = if y.rank() == 0 { vec![y.clone()] } else { y.cells(1) };
13418    let Some((last, rest)) = items.split_last() else {
13419        return Err(Error::domain("m`:3 needs an argument with items", span));
13420    };
13421    let mut acc = last.clone();
13422    for (i, item) in rest.iter().enumerate().rev() {
13423        acc = vs[i % vs.len()].dyad(item, &acc, ctx, span)?;
13424    }
13425    Ok(acc)
13426}
13427
13428/// `(f⌺w) y` (Dyalog's stencil): the window of `w` cells centred on each
13429/// cell of y, with the edges filled, and f applied to each. There is one
13430/// size per leading axis of y and the axes past them travel whole, so the
13431/// answer is framed by the axes the windows moved along.
13432fn stencil(u: &Verb, w: &[i64], y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
13433    if w.len() > y.rank() {
13434        return Err(Error::new(
13435            ErrorKind::Rank,
13436            format!("a stencil of {} axis/axes into a rank-{} value", w.len(), y.rank()),
13437            Some(span),
13438        ));
13439    }
13440    if w.iter().any(|&n| n <= 0) {
13441        return Err(Error::domain("a stencil window is a positive size", span));
13442    }
13443    let y = y.to_row_major();
13444    let k = w.len();
13445    let st = strides(&y.shape);
13446    let frame: Vec<usize> = y.shape[..k].to_vec();
13447    // The window's own shape: the sizes, then whatever the cell carries.
13448    let mut wshape: Vec<usize> = w.iter().map(|&n| n as usize).collect();
13449    wshape.extend_from_slice(&y.shape[k..]);
13450    let inner: usize = y.shape[k..].iter().product();
13451    let total: usize = frame.iter().product();
13452    let mut cells = Vec::with_capacity(total);
13453    let mut at = vec![0usize; frame.len()];
13454    let mut coord = vec![0usize; k];
13455    for _ in 0..total {
13456        let mut data = Data::empty(y.dtype());
13457        coord.iter_mut().for_each(|c| *c = 0);
13458        let count: usize = w.iter().map(|&n| n as usize).product();
13459        for _ in 0..count {
13460            let mut base = 0usize;
13461            let mut inside = true;
13462            for a in 0..k {
13463                let off = at[a] as i64 + coord[a] as i64 - (w[a] - 1) / 2;
13464                if off < 0 || off >= y.shape[a] as i64 {
13465                    inside = false;
13466                    break;
13467                }
13468                base += off as usize * st[a];
13469            }
13470            for j in 0..inner {
13471                if inside {
13472                    push_elem(&mut data, &y.data, base + j);
13473                } else {
13474                    data.push_fill();
13475                }
13476            }
13477            odometer(&mut coord, &wshape[..k]);
13478        }
13479        cells.push(u.monad(&Array::new(wshape.clone(), data), ctx, span)?);
13480        odometer(&mut at, &frame);
13481    }
13482    assemble(&frame, cells, span)
13483}
13484
13485/// Whether an insert settles its domain from the whole argument before it
13486/// cuts anything. J answers `2 %/\. 'abc'` with `ca` — a piece of one item
13487/// applies nothing, so the characters are never divided — but refuses
13488/// `2 +/\. 'abc'`, because a sum, a product, a running minimum or maximum
13489/// and an or are the five folds its special code types up front. The set is
13490/// the oracle's, exactly: `*./`, which looks like it belongs, answers.
13491fn folds_eagerly(u: &Verb) -> bool {
13492    let Verb::Reduce(inner) = u else { return false };
13493    matches!(
13494        **inner,
13495        Verb::Prim(Prim {
13496            dyad: DyadOp::Scalar(
13497                ScalarDyad::Add
13498                    | ScalarDyad::Mul
13499                    | ScalarDyad::Min
13500                    | ScalarDyad::Max
13501                    | ScalarDyad::Gcd
13502            ),
13503            ..
13504        })
13505    )
13506}
13507
13508/// `x u\. y`: u applied to y with every run of x consecutive items removed.
13509/// A run of x items has `1 + (#y) - x` places to sit, and that is how many
13510/// results there are.
13511fn outfix(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
13512    let k = one_int(x, "an outfix width", ctx.cfg.near(), span)?;
13513    let n = y.items() as i64;
13514    let list = as_list(y);
13515    // A positive width leaves out every run of x consecutive items, so
13516    // there are `1 + n - x` of them and none at all once x is longer than
13517    // the argument. A negative one leaves out NON-OVERLAPPING runs, the
13518    // last of them short where the length does not divide.
13519    // The widths are the program's own numbers, so the arithmetic that
13520    // turns one into a list of starts runs in i128: `_9223372036854775808`
13521    // has no negation in i64, and `n + step` overflows for a large step.
13522    let starts: Vec<i64> = if k < 0 {
13523        let step = i128::from(k.unsigned_abs());
13524        let count = (i128::from(n) + step - 1) / step;
13525        (0..count).map(|i| (i * step) as i64).collect()
13526    } else {
13527        (0..=(n - k)).collect()
13528    };
13529    let width = k.unsigned_abs() as usize;
13530    // A sum, a product, a running extremum and an or are the folds J has
13531    // special code for, and that code settles its domain from the WHOLE
13532    // argument before any piece is cut: `2 +/\. 'abc'` is a domain error
13533    // although every piece it leaves behind holds one character, and so are
13534    // `_2 +/\. 'ab'` and `4 +/\. 'abc'`, which fold nothing at all. Every
13535    // other fold is asked piece by piece, so `2 %/\. 'abc'` is `ca`. The
13536    // probe is spent on characters and boxes alone, since numeric data
13537    // never fails it -- and on nothing at all when the operand is not pure,
13538    // since a verb that writes must not write twice.
13539    if !list.dtype().is_numeric()
13540        && u.is_pure()
13541        && folds_eagerly(u)
13542        && n >= 1
13543        && (n >= 2 || !starts.is_empty())
13544    {
13545        // The question is whether the operand has a MEANING for this data,
13546        // and a fold of one item answers nothing: `+/ ,'a'` is that one
13547        // character, applying `+` to nothing. So an argument of one item is
13548        // asked with that item twice, which is the smallest fold that
13549        // really applies the operand.
13550        let probe =
13551            if n == 1 { select_items(&list, &[0, 0]) } else { list.clone() };
13552        u.monad(&probe, ctx, span)?;
13553    }
13554    // A width longer than the argument leaves no place for the run to sit.
13555    // The one run an empty argument has is the argument itself, and that is
13556    // the cell whose shape the answer keeps.
13557    if starts.is_empty() {
13558        let cell = u.is_pure().then(|| select_items(&list, &[]));
13559        return Ok(empty_frame(&[0], list.dtype(), cell, ctx, |cell, c| u.monad(cell, c, span)));
13560    }
13561    let mut cells = Vec::with_capacity(starts.len());
13562    for start in starts {
13563        let start = start as usize;
13564        let keep: Vec<usize> =
13565            (0..n as usize).filter(|&i| i < start || i >= start + width).collect();
13566        cells.push(u.monad(&select_items(&list, &keep), ctx, span)?);
13567    }
13568    assemble(&[cells.len()], cells, span)
13569}
13570
13571// ---------------------------------------------------------------- obverses
13572
13573/// The verb that undoes this one, where libjay knows of one.
13574///
13575/// This is J's obverse table, and it is deliberately a table rather than a
13576/// search: a verb is here only when its inverse is another verb libjay can
13577/// already write down. Everything built out of those — the compositions,
13578/// the bonds, `u^:n` — inverts by inverting its parts, so the table stays
13579/// small while `&.`, `&.:` and the negative powers reach a long way past
13580/// it. A verb that is not here has no obverse, and the diagnostic says so
13581/// by name.
13582pub(crate) fn obverse(v: &Verb) -> Option<Verb> {
13583    Some(match v {
13584        Verb::Prim(p) => prim_obverse(v, p)?,
13585        // An explicit obverse (`u :. v`) is the whole answer.
13586        Verb::WithObverse(_, w) => (**w).clone(),
13587        // A composition inverts by inverting its parts, in the other order.
13588        Verb::Atop(f, g) => {
13589            Verb::Atop(Box::new(obverse(g)?), Box::new(obverse(f)?))
13590        }
13591        Verb::Compose(f, g) | Verb::Beside(f, g) => {
13592            Verb::Atop(Box::new(obverse(g)?), Box::new(obverse(f)?))
13593        }
13594        Verb::Rank(f, r) => Verb::Rank(Box::new(obverse(f)?), *r),
13595        Verb::Fit(f, n) => Verb::Fit(Box::new(obverse(f)?), *n),
13596        // `u&.>` and `u¨` undo box by box: the boxing is its own inverse,
13597        // so only the verb inside one has to be turned round.
13598        Verb::Each(f, rule) => Verb::Each(Box::new(obverse(f)?), *rule),
13599        // `*/ y` is the product, and the product of a whole number is
13600        // undone by its prime factors.
13601        Verb::Reduce(f) if is_dyad(f, DyadOp::Scalar(ScalarDyad::Mul)) => named("q:")?,
13602        // The running sums and products, which invert into the differences
13603        // and the quotients between neighbours.
13604        Verb::Windowed(f, kind) => scan_obverse(f, *kind)?,
13605        // `u^:n` undone is `u^:_1` done n times.
13606        Verb::PowerN(f, Power::Times(n)) => {
13607            Verb::PowerN(Box::new(obverse(f)?), Power::Times(*n))
13608        }
13609        Verb::BondLeft(m, f) => bond_obverse(m, f, true)?,
13610        Verb::BondRight(f, n) => bond_obverse(n, f, false)?,
13611        _ => return None,
13612    })
13613}
13614
13615/// A J primitive by its spelling, for the obverses that are one.
13616fn named(spelling: &'static str) -> Option<Verb> {
13617    crate::frontend::j::verb_named(spelling)
13618}
13619
13620/// A verb built here rather than looked up: the inverses J itself spells
13621/// only as `u^:_1`, so they carry that spelling as their name.
13622fn made(name: &'static str, monad: MonadOp, ranks: [i64; 3]) -> Verb {
13623    Verb::Prim(Prim { name, monad, dyad: DyadOp::None, ranks })
13624}
13625
13626fn is_dyad(v: &Verb, op: DyadOp) -> bool {
13627    matches!(v, Verb::Prim(p) if p.dyad == op)
13628}
13629
13630fn atop(f: Verb, g: Verb) -> Verb {
13631    Verb::Atop(Box::new(f), Box::new(g))
13632}
13633
13634/// The obverse of a primitive.
13635fn prim_obverse(v: &Verb, p: &Prim) -> Option<Verb> {
13636    use ScalarMonad as SM;
13637    // Every one of these is its own inverse, whichever language spelled it:
13638    // the verb itself is the answer, so no name is looked up (an APL glyph
13639    // has no entry in J's table). Grade sends a permutation to the
13640    // permutation that undoes it, the cycles of `C.` convert back, and a
13641    // matrix inverse, a set of polynomial roots and the identity verbs all
13642    // return where they came from.
13643    if matches!(
13644        p.monad,
13645        MonadOp::Scalar(SM::Conj | SM::Neg | SM::Recip | SM::OneMinus)
13646            | MonadOp::Reverse
13647            | MonadOp::TransposeAxes
13648            | MonadOp::GradeUp { .. }
13649            | MonadOp::CycleForm
13650            | MonadOp::MatrixInverse
13651            | MonadOp::PolyRoots
13652            | MonadOp::Same
13653    ) {
13654        return Some(v.clone());
13655    }
13656    // `x # y` undone with the same x is the expansion: the items come back
13657    // where the ones stand and a fill takes every place a zero left. It has
13658    // no monadic meaning, since `# y` counts and a count says nothing about
13659    // what was counted.
13660    if p.dyad == DyadOp::Copy {
13661        return Some(expand_verb());
13662    }
13663    let built = match p.monad {
13664        // `j. y` turns y a quarter turn about the origin; turning it back
13665        // is a quarter turn the other way, which is `-@j.`.
13666        MonadOp::Scalar(SM::Imaginary) => atop(named("-")?, named("j.")?),
13667        // `r. y` is `^ 0j1 * y`, so the angle comes back as the logarithm
13668        // turned the same quarter turn back.
13669        MonadOp::Scalar(SM::Polar) => {
13670            atop(atop(named("-")?, named("j.")?), named("^.")?)
13671        }
13672        // `o. y` multiplies by pi, and the reference undoes it by
13673        // multiplying by the reciprocal rather than dividing.
13674        MonadOp::Scalar(SM::Pi) => Verb::BondLeft(
13675            Array::scalar_f64(std::f64::consts::FRAC_1_PI),
13676            Box::new(named("*")?),
13677        ),
13678        // The two readings of a complex number as a pair of reals: the
13679        // pair folds back together under the verb that made it.
13680        MonadOp::ComplexParts { polar } => Verb::Rank(
13681            Box::new(Verb::Reduce(Box::new(named(if polar { "r." } else { "j." })?))),
13682            [1, RANK_INF, RANK_INF],
13683        ),
13684        // Grading down is grading up over the reversed argument.
13685        MonadOp::GradeDown { origin } => atop(
13686            Verb::Prim(Prim {
13687                name: "/:",
13688                monad: MonadOp::GradeUp { origin },
13689                dyad: DyadOp::GradeSelect { down: false },
13690                ranks: [RANK_INF, RANK_INF, RANK_INF],
13691            }),
13692            named("|.")?,
13693        ),
13694        // A list of prime factors multiplies back into its number, one row
13695        // at a time.
13696        MonadOp::PrimeFactors => {
13697            Verb::Rank(Box::new(Verb::Reduce(Box::new(named("*")?))), [1, RANK_INF, RANK_INF])
13698        }
13699        // `;: y` cuts a character list into words; putting a blank after
13700        // each word and razing them joins it back, less the trailing blank.
13701        MonadOp::Words => atop(
13702            named("}:")?,
13703            atop(
13704                named(";")?,
13705                Verb::Each(
13706                    Box::new(Verb::BondRight(
13707                        Box::new(named(",")?),
13708                        Array::from_chars(vec![' ']),
13709                    )),
13710                    Enclose::Always,
13711                ),
13712            ),
13713        ),
13714        // The forms that carry their own inverse in the same spelling.
13715        MonadOp::ToExact => Verb::BondLeft(Array::scalar_i64(-1), Box::new(named("x:")?)),
13716        MonadOp::Unicode { .. } => {
13717            Verb::BondLeft(Array::scalar_i64(3), Box::new(named("u:")?))
13718        }
13719        MonadOp::Symbols => Verb::BondLeft(Array::scalar_i64(5), Box::new(named("s:")?)),
13720        // The three the reference spells only as a negative power.
13721        MonadOp::NthPrime => made("p:^:_1", MonadOp::PrimeCount, [0, 0, 0]),
13722        MonadOp::Sparse => {
13723            made("$.^:_1", MonadOp::Dense, [RANK_INF, RANK_INF, RANK_INF])
13724        }
13725        MonadOp::Indices { origin: 0, boxed_coords: false } => {
13726            made("I.^:_1", MonadOp::IndicesInverse, [1, RANK_INF, RANK_INF])
13727        }
13728        // Formatting and evaluating undo one another in whichever language
13729        // spelled them: `":` with `".`, `⍕` with `⍎`.
13730        MonadOp::Format if p.name == "⍕" => Verb::Prim(Prim {
13731            name: "⍎",
13732            monad: MonadOp::Execute { apl: true },
13733            dyad: DyadOp::None,
13734            ranks: [1, RANK_INF, RANK_INF],
13735        }),
13736        MonadOp::Format => named("\".")?,
13737        MonadOp::Execute { apl: true } => Verb::Prim(Prim {
13738            name: "⍕",
13739            monad: MonadOp::Format,
13740            dyad: DyadOp::FormatSpec,
13741            ranks: [RANK_INF, 1, RANK_INF],
13742        }),
13743        MonadOp::Execute { apl: false } => named("\":")?,
13744        _ => {
13745            let by_monad: Option<&'static str> = match p.monad {
13746                MonadOp::Scalar(SM::Exp) => Some("^."),
13747                MonadOp::Scalar(SM::Ln) => Some("^"),
13748                MonadOp::Scalar(SM::Sqrt) => Some("*:"),
13749                MonadOp::Scalar(SM::Square) => Some("%:"),
13750                MonadOp::Scalar(SM::Double) => Some("-:"),
13751                MonadOp::Scalar(SM::Halve) => Some("+:"),
13752                MonadOp::Scalar(SM::Inc) => Some("<:"),
13753                MonadOp::Scalar(SM::Dec) => Some(">:"),
13754                MonadOp::Enclose(_) => Some(">"),
13755                MonadOp::Open => Some("<"),
13756                MonadOp::DecodeBits => Some("#:"),
13757                MonadOp::EncodeBits => Some("#."),
13758                MonadOp::Itemize => Some("{."),
13759                MonadOp::Head => Some(",:"),
13760                _ => None,
13761            };
13762            named(by_monad?)?
13763        }
13764    };
13765    Some(built)
13766}
13767
13768/// `x #^:_1 y`: the expansion, which is what undoes `x # y`.
13769fn expand_verb() -> Verb {
13770    Verb::Prim(Prim {
13771        name: "#^:_1",
13772        monad: MonadOp::None,
13773        dyad: DyadOp::Expand,
13774        ranks: [RANK_INF, 1, RANK_INF],
13775    })
13776}
13777
13778/// The obverse of a running fold — `+/\`, `-/\.` and their kin.
13779///
13780/// A running sum inverts into the differences between neighbours, a running
13781/// product into the quotients: the argument against itself shifted one
13782/// place, the fill being the operation's identity. The subtracting and
13783/// dividing folds alternate, so their answers carry one further pass over
13784/// the signs `1 _1 1 _1 …`.
13785fn scan_obverse(f: &Verb, kind: WindowKind) -> Option<Verb> {
13786    use ScalarDyad as SD;
13787    let Verb::Reduce(inner) = f else { return None };
13788    let Verb::Prim(p) = &**inner else { return None };
13789    let DyadOp::Scalar(op) = p.dyad else { return None };
13790    let suffix = match kind {
13791        WindowKind::Prefix | WindowKind::Scan => false,
13792        WindowKind::Suffix => true,
13793    };
13794    // The neighbour: one place to the right for a prefix fold, one to the
13795    // left for a suffix one, the vacated place taking the fill.
13796    let fill = match op {
13797        SD::Add | SD::Sub => 0.0,
13798        SD::Mul | SD::DivJ | SD::DivApl => 1.0,
13799        _ => return None,
13800    };
13801    let shift = Verb::ShiftFill(Array::scalar_f64(fill));
13802    let neighbour = if suffix {
13803        Verb::BondLeft(Array::scalar_i64(1), Box::new(shift))
13804    } else {
13805        shift
13806    };
13807    // What takes the argument back to its neighbour: the inverse of the
13808    // fold for a prefix, the fold itself for a suffix.
13809    let step = match (op, suffix) {
13810        (SD::Add, false) | (SD::Sub, false) => named("-")?,
13811        (SD::Add, true) => named("-")?,
13812        (SD::Sub, true) => named("+")?,
13813        (SD::Mul, _) | (SD::DivJ | SD::DivApl, false) => named("%")?,
13814        (SD::DivJ | SD::DivApl, true) => named("*")?,
13815        _ => return None,
13816    };
13817    let differences = Verb::Hook(Box::new(step), Box::new(neighbour));
13818    // A prefix fold under subtraction or division alternates, so every
13819    // second answer is turned round again.
13820    let alternate = matches!((op, suffix), (SD::Sub, false) | (SD::DivJ | SD::DivApl, false));
13821    if !alternate {
13822        return Some(differences);
13823    }
13824    let signs = atop(
13825        Verb::BondRight(Box::new(named("$")?), Array::from_i64(vec![1, -1])),
13826        named("#")?,
13827    );
13828    let apply = if matches!(op, SD::Sub) { named("*")? } else { named("^")? };
13829    Some(Verb::Fork(Box::new(differences), Box::new(apply), Box::new(signs)))
13830}
13831
13832/// The obverse of a bonded arithmetic verb. `left` says which side the noun
13833/// was bonded to, which is what tells `n - y` (its own inverse) from
13834/// `y - n` (whose inverse adds).
13835fn bond_obverse(n: &Array, f: &Verb, left: bool) -> Option<Verb> {
13836    // `u~&n` is `n&u` written the other way round, so it inverts the same
13837    // way. The reference gives `n&u~` no obverse, and neither does this.
13838    if let Verb::Commute(g) = f {
13839        if !left {
13840            return bond_obverse(n, g, true);
13841        }
13842        return None;
13843    }
13844    if let Some(v) = structural_bond_obverse(n, f, left) {
13845        return Some(v);
13846    }
13847    let Verb::Prim(p) = f else { return None };
13848    let bond = |name: &'static str, arg: &Array| -> Option<Verb> {
13849        let g = named(name)?;
13850        Some(if left {
13851            Verb::BondLeft(arg.clone(), Box::new(g))
13852        } else {
13853            Verb::BondRight(Box::new(g), arg.clone())
13854        })
13855    };
13856    use ScalarDyad as SD;
13857    let DyadOp::Scalar(op) = p.dyad else { return None };
13858    if matches!(op, SD::Circle) {
13859        // `n o. y` is undone by `(-n) o. y`: the circle functions are
13860        // numbered so that the negative index is the inverse.
13861        return left.then(|| Some(Verb::BondLeft(negated(n)?, Box::new(named("o.")?))))?;
13862    }
13863    match (op, left) {
13864        // `n - y` and `n % y` undo themselves; the other side does not.
13865        (SD::Sub | SD::DivJ | SD::DivApl, true) => bond(p.name, n),
13866        // Adding or multiplying is undone by taking the noun off the
13867        // RIGHT, whichever side it was bonded to: `2&+` is undone by `-&2`
13868        // and not by `2&-`.
13869        (SD::Add, _) => Some(Verb::BondRight(Box::new(named("-")?), n.clone())),
13870        (SD::Mul, _) => Some(Verb::BondRight(Box::new(named("%")?), n.clone())),
13871        (SD::Sub, false) => bond("+", n),
13872        (SD::DivJ | SD::DivApl, false) => bond("*", n),
13873        // `y ^ n` is undone by the n-th root; `n ^ y` by the base-n log.
13874        (SD::Pow, false) => Some(Verb::BondLeft(n.clone(), Box::new(named("%:")?))),
13875        (SD::Pow, true) => Some(Verb::BondLeft(n.clone(), Box::new(named("^.")?))),
13876        // `n ^. y` is the logarithm to the base n, which raising n to the
13877        // answer turns back; `n %: y` is the n-th root, which the n-th
13878        // POWER turns back, and the noun changes sides for it.
13879        (SD::Log, true) => Some(Verb::BondLeft(n.clone(), Box::new(named("^")?))),
13880        (SD::Root, true) => Some(Verb::BondRight(Box::new(named("^")?), n.clone())),
13881        // `y ^. n` is the logarithm of n to the base y, which the y-th root
13882        // of n turns back, and the other way round.
13883        (SD::Log, false) => Some(Verb::BondRight(Box::new(named("%:")?), n.clone())),
13884        (SD::Root, false) => Some(Verb::BondRight(Box::new(named("^.")?), n.clone())),
13885        _ => None,
13886    }
13887}
13888
13889/// The noun with every value negated, for the bonds whose obverse is the
13890/// same verb with the opposite parameter.
13891fn negated(n: &Array) -> Option<Array> {
13892    if let Some(v) = n.to_i64_vec() {
13893        let out: Vec<i64> = v.iter().map(|&k| -k).collect();
13894        return Some(Array::new(n.shape.clone(), Data::I64(out.into())));
13895    }
13896    let v = n.to_f64_vec()?;
13897    let out: Vec<f64> = v.iter().map(|&k| -k).collect();
13898    Some(Array::new(n.shape.clone(), Data::F64(out.into())))
13899}
13900
13901/// The one number a bond's noun holds, for the bonds whose obverse needs
13902/// its value rather than only its shape.
13903fn one_number(n: &Array) -> Option<f64> {
13904    match n.to_f64_vec()?[..] {
13905        [v] if n.rank() <= 1 => Some(v),
13906        _ => None,
13907    }
13908}
13909
13910/// The obverse of a bond whose verb rearranges rather than computes: the
13911/// rotations, the drops and appends, the base conversions and the two
13912/// permutation forms.
13913fn structural_bond_obverse(n: &Array, f: &Verb, left: bool) -> Option<Verb> {
13914    // APL wraps some of its primitives in the rank that picks the axis, so
13915    // the primitive is looked for under one; the rules that keep the verb
13916    // and change only the noun keep that wrapper with it.
13917    let p = match f {
13918        Verb::Prim(p) => p,
13919        Verb::Rank(inner, _) => match &**inner {
13920            Verb::Prim(p) => p,
13921            _ => return None,
13922        },
13923        _ => return None,
13924    };
13925    match (p.dyad, left) {
13926        // `n |. y` is undone by rotating the other way.
13927        (DyadOp::Rotate | DyadOp::RotateApl { .. }, true) => {
13928            Some(Verb::BondLeft(negated(n)?, Box::new(f.clone())))
13929        }
13930        // `x # y` bonded keeps its expansion.
13931        (DyadOp::Copy, true) => Some(Verb::BondLeft(n.clone(), Box::new(expand_verb()))),
13932        // Appending a fixed noun is undone by dropping as many items as it
13933        // brought — off the end when it was appended there, off the front
13934        // when it went in front.
13935        (DyadOp::AppendLeading | DyadOp::AppendLast, _) => {
13936            let items = if n.rank() == 0 { 1 } else { n.shape[0] } as i64;
13937            let count = if left { items } else { -items };
13938            Some(Verb::BondLeft(Array::scalar_i64(count), Box::new(named("}.")?)))
13939        }
13940        // `n }. y` is undone by taking back what was dropped: as many items
13941        // as the argument has now plus the ones that went, from the end the
13942        // drop did not touch, so the vacated places take a fill.
13943        (DyadOp::Drop, true) => {
13944            let k = one_number(n)?;
13945            let size = Verb::Atop(
13946                Box::new(Verb::BondLeft(Array::scalar_f64(k.abs()), Box::new(named("+")?))),
13947                Box::new(named("#")?),
13948            );
13949            let width = if k >= 0.0 { atop(named("-")?, size) } else { size };
13950            Some(Verb::Hook(
13951                Box::new(Verb::Commute(Box::new(named("{.")?))),
13952                Box::new(width),
13953            ))
13954        }
13955        // `n #. y` reads a list of digits in base n; undoing it writes the
13956        // digits back, in as many places as the largest value asks for.
13957        (DyadOp::Decode, true) => {
13958            let width = atop(
13959                Verb::BondRight(Box::new(named("$")?), n.clone()),
13960                atop(
13961                    named(">:")?,
13962                    atop(
13963                        named("<.")?,
13964                        atop(
13965                            Verb::BondLeft(n.clone(), Box::new(named("^.")?)),
13966                            atop(
13967                                Verb::BondLeft(Array::scalar_i64(1), Box::new(named(">.")?)),
13968                                atop(
13969                                    Verb::Reduce(Box::new(named(">.")?)),
13970                                    atop(named("|")?, named(",")?),
13971                                ),
13972                            ),
13973                        ),
13974                    ),
13975                ),
13976            );
13977            Some(Verb::Fork(Box::new(width), Box::new(named("#:")?), Box::new(named("]")?)))
13978        }
13979        // `n #: y` writes the digits, and reading them back is `n #. y`.
13980        (DyadOp::Encode, true) => Some(Verb::BondLeft(n.clone(), Box::new(named("#.")?))),
13981        // `n A. y` and `n C. y` permute; the permutation that undoes them is
13982        // the one they make of `i. # y`, graded.
13983        (DyadOp::AnagramFrom | DyadOp::Permute, true) => {
13984            let spelling = if p.dyad == DyadOp::AnagramFrom { "A." } else { "C." };
13985            let inverse = atop(
13986                atop(named("/:")?, Verb::BondLeft(n.clone(), Box::new(named(spelling)?))),
13987                atop(named("i.")?, named("#")?),
13988            );
13989            Some(Verb::Fork(Box::new(inverse), Box::new(named("{")?), Box::new(named("]")?)))
13990        }
13991        _ => None,
13992    }
13993}
13994
13995// ------------------------------------------------- classification and sets
13996
13997/// `= y`: one row per distinct item, marking where that item stands. A
13998/// scalar has one item, so it answers a 1×1 table.
13999fn self_classify(y: &Array, tol: Tol) -> Array {
14000    let items = if y.rank() == 0 { 1 } else { y.items() };
14001    let keys = nub(&as_list(y), tol);
14002    let rows = keys.items();
14003    let mut out = Vec::with_capacity(rows * items);
14004    for i in 0..rows {
14005        let key = item_or_self(&keys, i);
14006        for j in 0..items {
14007            out.push(arrays_match(&key, &item_or_self(y, j), tol) as u8);
14008        }
14009    }
14010    Array::new(vec![rows, items], Data::Bool(out.into()))
14011}
14012
14013/// `~: y` / `≠ y`: 1 where a value has not been seen before.
14014///
14015/// The two languages count different things. J's sieve runs over ITEMS and
14016/// answers one bit per item, so a matrix gives a vector. APL's runs over
14017/// the ELEMENTS in ravel order and keeps the argument's own shape, so a
14018/// matrix gives a matrix and a scalar gives a scalar.
14019fn nub_sieve(y: &Array, tol: Tol, lang: crate::Lang) -> Array {
14020    let by_element = lang == crate::Lang::Apl;
14021    let n = if by_element {
14022        y.count()
14023    } else if y.rank() == 0 {
14024        1
14025    } else {
14026        y.items()
14027    };
14028    let mut seen: Vec<Array> = Vec::new();
14029    let mut out = Vec::with_capacity(n);
14030    for i in 0..n {
14031        let cell = if by_element {
14032            Array::new(Vec::new(), y.data.slice(i, i + 1))
14033        } else {
14034            item_or_self(y, i)
14035        };
14036        let fresh = !seen.iter().any(|s| arrays_match(s, &cell, tol));
14037        if fresh {
14038            seen.push(cell);
14039        }
14040        out.push(fresh as u8);
14041    }
14042    let shape = if by_element { y.shape.clone() } else { vec![n] };
14043    Array::new(shape, Data::Bool(out.into()))
14044}
14045
14046/// A rank-0 argument as the one-item list it behaves as for the set verbs.
14047fn as_list(y: &Array) -> Array {
14048    if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() }
14049}
14050
14051/// The values of `y` that an item of shape `item_rank` could match: y's
14052/// cells of that rank, framed by whatever axes are left. A y with no room
14053/// for a frame is one such value, which is what lets `(i.3 2) -. 2 3`
14054/// remove the row rather than nothing.
14055fn conforming_cells(y: &Array, item_rank: usize) -> Vec<Array> {
14056    let frame_rank = y.rank().saturating_sub(item_rank);
14057    let nf: usize = y.shape[..frame_rank].iter().product();
14058    (0..nf).map(|i| y.cell_at(frame_rank, i)).collect()
14059}
14060
14061/// Which items of `y` occur among the values of `x` that could match one.
14062fn item_marks(y: &Array, x: &Array, tol: Tol) -> Vec<bool> {
14063    let n = if y.rank() == 0 { 1 } else { y.items() };
14064    let item_rank = y.rank().saturating_sub(1);
14065    let against = conforming_cells(x, item_rank);
14066    (0..n)
14067        .map(|i| {
14068            let cell = item_or_self(y, i);
14069            against.iter().any(|c| arrays_match(&cell, c, tol))
14070        })
14071        .collect()
14072}
14073
14074/// `x -. y` / `x ~ y`: x's items with the ones y also has removed.
14075fn set_less(x: &Array, y: &Array, tol: Tol) -> Array {
14076    let xs = as_list(x);
14077    let marks = item_marks(&xs, y, tol);
14078    let keep: Vec<usize> = (0..marks.len()).filter(|&i| !marks[i]).collect();
14079    select_items(&xs, &keep)
14080}
14081
14082/// APL's set functions read their arguments as lists and refuse anything
14083/// deeper: `1 2∩2 3⍴⍳6` is a RANK ERROR where J's `-.` and `~.` would work
14084/// on the items of a table.
14085fn set_rank(cfg: EvalCfg, what: &str, x: &Array, y: &Array, span: Span) -> Result<()> {
14086    if cfg.rules.lang == crate::Lang::Apl && (x.rank() > 1 || y.rank() > 1) {
14087        return Err(Error::new(
14088            ErrorKind::Rank,
14089            format!("{what} takes vectors, not rank {} and rank {}", x.rank(), y.rank()),
14090            Some(span),
14091        ));
14092    }
14093    Ok(())
14094}
14095
14096/// `x ∩ y`: x's items that y also has, in x's order and with x's repeats.
14097fn intersect_items(x: &Array, y: &Array, tol: Tol) -> Array {
14098    let xs = as_list(x);
14099    let marks = item_marks(&xs, y, tol);
14100    let keep: Vec<usize> = (0..marks.len()).filter(|&i| marks[i]).collect();
14101    select_items(&xs, &keep)
14102}
14103
14104/// `x ∪ y`: x's items, then the items of y that are new. x keeps whatever
14105/// repeats it has; APL's union only sieves the right argument.
14106fn union_items(x: &Array, y: &Array, tol: Tol, span: Span) -> Result<Array> {
14107    let xs = as_list(x);
14108    let ys = as_list(y);
14109    let marks = item_marks(&ys, &xs, tol);
14110    let mut extra: Vec<usize> = Vec::new();
14111    for (i, &seen) in marks.iter().enumerate() {
14112        if seen {
14113            continue;
14114        }
14115        let cell = item_or_self(&ys, i);
14116        if !extra.iter().any(|&j| arrays_match(&item_or_self(&ys, j), &cell, tol)) {
14117            extra.push(i);
14118        }
14119    }
14120    catenate(&xs, &select_items(&ys, &extra), true, false, span)
14121}
14122
14123/// `x E. y` / `x ⍷ y`: 1 at each position of y where a copy of x begins.
14124/// The answer is shaped like y, and the search runs over all of y's axes at
14125/// once, so a table is looked for inside a table. A pattern that would run
14126/// off an edge matches nowhere; an EMPTY pattern matches everywhere, being
14127/// a run of no elements.
14128///
14129/// The two languages align the pattern differently: J wants the two ranks
14130/// to agree, counting a scalar pattern as a one-element list, while APL
14131/// pads the pattern with leading axes of one and takes any rank up to y's.
14132fn find_seq(x: &Array, y: &Array, tol: Tol, apl: bool, span: Span) -> Result<Array> {
14133    let (xr, yr) = (x.rank(), y.rank());
14134    // J reads an atom as a one-item list on BOTH sides, so a pattern of
14135    // one atom has exactly one place to sit in an argument of one atom:
14136    // `0 E. 5` is 0 and `1 E. 1` is 1, both of them scalars.
14137    if !apl && xr == 0 && yr == 0 {
14138        let hit = arrays_match(x, y, tol);
14139        return Ok(Array::new(Vec::new(), Data::Bool(vec![u8::from(hit)].into())));
14140    }
14141    if apl && xr > yr {
14142        // A pattern with more axes than the argument fits nowhere in it.
14143        return Ok(Array::new(y.shape.clone(), Data::Bool(vec![0u8; y.count()].into())));
14144    }
14145    if !apl && xr.max(1) != yr {
14146        return Err(Error::new(
14147            ErrorKind::Rank,
14148            format!("a rank-{xr} pattern in a rank-{yr} argument"),
14149            Some(span),
14150        ));
14151    }
14152    let mut pattern = vec![1usize; yr];
14153    pattern[yr - xr..].copy_from_slice(&x.shape);
14154    let n = y.count();
14155    let mut out = vec![0u8; n];
14156    let (xrm, yrm) = (x.to_row_major(), y.to_row_major());
14157    let yst = strides(&y.shape);
14158    let cells: usize = pattern.iter().product();
14159    let mut at = vec![0usize; yr];
14160    for slot in out.iter_mut() {
14161        if (0..yr).all(|a| at[a] + pattern[a] <= y.shape[a]) {
14162            let mut off = vec![0usize; yr];
14163            let mut hit = true;
14164            for k in 0..cells {
14165                let i: usize = (0..yr).map(|a| (at[a] + off[a]) * yst[a]).sum();
14166                if !arrays_match(&atom(&xrm, k), &atom(&yrm, i), tol) {
14167                    hit = false;
14168                    break;
14169                }
14170                odometer(&mut off, &pattern);
14171            }
14172            *slot = hit as u8;
14173        }
14174        odometer(&mut at, &y.shape);
14175    }
14176    Ok(Array::new(y.shape.clone(), Data::Bool(out.into())))
14177}
14178
14179/// `+:` and `*:` dyadically, and APL's `⍱` and `⍲`: both arguments must
14180/// already be booleans, which is the only domain either reference gives
14181/// them.
14182fn bool_dyad(op: BoolDyad, x: &Array, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
14183    let bit = |a: &Array| -> Result<u8> {
14184        match a.to_i64_vec().as_deref() {
14185            Some([0]) => Ok(0),
14186            Some([1]) => Ok(1),
14187            _ => Err(Error::domain("this verb reads values of 0 or 1", span)),
14188        }
14189    };
14190    let _ = cfg;
14191    let (a, b) = (bit(x)?, bit(y)?);
14192    let v = match op {
14193        BoolDyad::Nor => u8::from(a == 0 && b == 0),
14194        BoolDyad::Nand => u8::from(a == 0 || b == 0),
14195    };
14196    Ok(Array::new(vec![], Data::Bool(vec![v].into())))
14197}
14198
14199// ------------------------------------------------------------ permutations
14200
14201/// The ranks of y's items: the position each would take in a stable sort.
14202/// This is the permutation `A.` reports the index of, which is why a list
14203/// that is not itself a permutation still has an anagram index.
14204fn item_ranks(y: &Array, rules: Rules, span: Span) -> Result<Vec<usize>> {
14205    check_gradable(y, rules, span)?;
14206    if !y.dtype().is_numeric() {
14207        return Err(Error::domain("an anagram index needs numbers", span));
14208    }
14209    let order = grade_order(&as_list(y), false, Grading::of(rules, rules.tol()));
14210    let mut ranks = vec![0usize; order.len()];
14211    for (place, &i) in order.iter().enumerate() {
14212        ranks[i] = place;
14213    }
14214    Ok(ranks)
14215}
14216
14217/// `A. y`: where the permutation y's items rank as stands in the
14218/// lexicographic list of the permutations of that length.
14219fn anagram_index(y: &Array, rules: Rules, span: Span) -> Result<Array> {
14220    let ranks = item_ranks(y, rules, span)?;
14221    let n = ranks.len();
14222    let mut index: i128 = 0;
14223    for i in 0..n {
14224        let smaller = ranks[i + 1..].iter().filter(|&&r| r < ranks[i]).count() as i128;
14225        index = index
14226            .checked_mul((n - i) as i128)
14227            .and_then(|v| v.checked_add(smaller))
14228            .ok_or_else(|| Error::not_yet("an anagram index too large for an integer", span))?;
14229    }
14230    i64::try_from(index)
14231        .map(Array::scalar_i64)
14232        .map_err(|_| Error::not_yet("an anagram index too large for an integer", span))
14233}
14234
14235/// `x A. y`: y's items in the order the x-th permutation puts them. A
14236/// negative x counts back from the last permutation, as J's does.
14237fn anagram_from(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14238    let ys = as_list(y);
14239    let n = ys.items();
14240    let mut total: i128 = 1;
14241    for k in 1..=n as i128 {
14242        total = total
14243            .checked_mul(k)
14244            .ok_or_else(|| Error::not_yet("permuting more items than an integer counts", span))?;
14245    }
14246    // The index is an integer wherever it picks an item. With no item to
14247    // permute there is exactly one arrangement and no digit to read from
14248    // the index, so J holds a number to the range alone: `0.5 A. i.0` is
14249    // the empty and `1.5 A. i.0` is out of range. A character or a box is
14250    // no index either way.
14251    let out_of_range = |want: &dyn std::fmt::Display| {
14252        Error::domain(
14253            format!("permutation {want} is out of range: {n} items have {total} of them"),
14254            span,
14255        )
14256    };
14257    let mut at: i128 = match x.to_i64_vec_near(near) {
14258        Some(v) => {
14259            let want = i128::from(
14260                *v.first().ok_or_else(|| Error::internal("anagram with no index"))?,
14261            );
14262            let at = if want < 0 { want + total } else { want };
14263            if at < 0 || at >= total {
14264                return Err(out_of_range(&want));
14265            }
14266            at
14267        }
14268        None => {
14269            if n != 0 {
14270                return Err(Error::domain("an anagram index must be an integer", span));
14271            }
14272            let want = *x
14273                .to_f64_vec()
14274                .as_deref()
14275                .and_then(<[f64]>::first)
14276                .ok_or_else(|| Error::domain("an anagram index must be an integer", span))?;
14277            let at = if want < 0.0 { want + total as f64 } else { want };
14278            if !(0.0..total as f64).contains(&at) {
14279                return Err(out_of_range(&want));
14280            }
14281            at as i128
14282        }
14283    };
14284    // The factorial number system, read most significant digit first: each
14285    // digit picks one of the items still unused.
14286    let mut pool: Vec<usize> = (0..n).collect();
14287    let mut order = Vec::with_capacity(n);
14288    let mut fact = total;
14289    for i in 0..n {
14290        fact /= (n - i) as i128;
14291        let d = (at / fact) as usize;
14292        at %= fact;
14293        order.push(pool.remove(d));
14294    }
14295    Ok(select_items(&ys, &order))
14296}
14297
14298/// `C. y`: the two directions between a direct permutation and its cycles.
14299/// A boxed argument holds cycles and answers the permutation; anything else
14300/// is a permutation and answers its cycles. A list shorter than the
14301/// permutation it names stands for one over `1 + >./ y` items, so
14302/// `C. 3 4 2` is the cycles of `0 1 3 4 2`.
14303fn cycle_form(y: &Array, near: NearInt, span: Span) -> Result<Array> {
14304    if y.dtype() == DType::Box {
14305        let perm = cycles_to_direct(y, None, near, span)?;
14306        return Ok(Array::from_i64(perm.iter().map(|&i| i as i64).collect()));
14307    }
14308    let n = permutation_span(y, near, span)?;
14309    let perm = direct_permutation_of(y, n, near, span)?;
14310    let mut boxes: Vec<Array> = Vec::new();
14311    let mut done = vec![false; perm.len()];
14312    for start in 0..perm.len() {
14313        if done[start] {
14314            continue;
14315        }
14316        let mut cycle = Vec::new();
14317        let mut at = start;
14318        while !done[at] {
14319            done[at] = true;
14320            cycle.push(at);
14321            at = perm[at];
14322        }
14323        // J writes each cycle starting at its largest element, and lists
14324        // the cycles in order of those.
14325        let top = cycle.iter().position(|&v| v == *cycle.iter().max().unwrap()).unwrap();
14326        cycle.rotate_left(top);
14327        boxes.push(Array::boxed(Array::from_i64(
14328            cycle.iter().map(|&i| i as i64).collect(),
14329        )));
14330    }
14331    boxes.sort_by_key(|b| b.as_boxes().map(|s| s[0].to_i64_vec().unwrap()[0]).unwrap_or(0));
14332    let n = boxes.len();
14333    let inner: Vec<Array> =
14334        boxes.into_iter().map(|b| b.as_boxes().unwrap()[0].clone()).collect();
14335    Ok(Array::new(vec![n], Data::Box(inner.into())))
14336}
14337
14338/// A direct permutation of `n` items, from a list that may be shorter than
14339/// one. A short list is J's ABBREVIATED permutation: the items it never
14340/// mentions come first, in ascending order, and the list itself is the
14341/// tail. `3 4 2` over five items is `0 1 3 4 2`; `2` over five is the same
14342/// permutation again, and `2 3` over four is the identity.
14343///
14344/// `n` is the count the context supplies — the length of the argument being
14345/// permuted, or for `C. y` one past the largest index the list names.
14346fn direct_permutation_of(y: &Array, n: usize, near: NearInt, span: Span) -> Result<Vec<usize>> {
14347    let v = y
14348        .to_i64_vec_near(near)
14349        .ok_or_else(|| Error::domain("a permutation is a list of integers", span))?;
14350    let mut seen = vec![false; n];
14351    let mut tail = Vec::with_capacity(v.len());
14352    for &i in &v {
14353        let k = usize::try_from(i).ok().filter(|&k| k < n && !seen[k]).ok_or_else(|| {
14354            Error::domain(format!("{i} does not belong to a permutation of {n} items"), span)
14355        })?;
14356        seen[k] = true;
14357        tail.push(k);
14358    }
14359    let mut out: Vec<usize> = (0..n).filter(|&k| !seen[k]).collect();
14360    out.append(&mut tail);
14361    Ok(out)
14362}
14363
14364/// How many items a permutation list stands for on its own: one past the
14365/// largest index it names, and never fewer than the indices it has.
14366fn permutation_span(y: &Array, near: NearInt, span: Span) -> Result<usize> {
14367    let v = y
14368        .to_i64_vec_near(near)
14369        .ok_or_else(|| Error::domain("a permutation is a list of integers", span))?;
14370    let top = v.iter().copied().max().unwrap_or(-1).saturating_add(1).max(0) as u128;
14371    Ok(crate::limits::count(top, span)?.max(v.len()))
14372}
14373
14374/// The direct permutation a boxed list of cycles stands for. Its length is
14375/// one past the largest element any cycle mentions; everything unmentioned
14376/// stays where it is.
14377///
14378/// `within` is how many items the cycles are about to permute, when the
14379/// caller has them: an element then counts back from the end where it is
14380/// negative and names an item that exists, and the permutation is never
14381/// longer than that. Without it — `C. y` alone, which answers a permutation
14382/// of whatever length the cycles ask for — an element is a plain index, and
14383/// the length it asks for is held to the element ceiling rather than
14384/// allocated on trust.
14385fn cycles_to_direct(
14386    y: &Array,
14387    within: Option<usize>,
14388    near: NearInt,
14389    span: Span,
14390) -> Result<Vec<usize>> {
14391    let boxes = y.as_boxes().ok_or_else(|| Error::internal("cycles from a simple array"))?;
14392    let mut cycles: Vec<Vec<usize>> = Vec::new();
14393    let mut top = 0usize;
14394    for b in boxes {
14395        let v = b
14396            .to_i64_vec_near(near)
14397            .ok_or_else(|| Error::domain("a cycle is a list of integers", span))?;
14398        let mut cycle = Vec::with_capacity(v.len());
14399        for &i in &v {
14400            let k = match within {
14401                Some(n) => {
14402                    let at = if i < 0 { i.checked_add(n as i64) } else { Some(i) };
14403                    usize::try_from(at.unwrap_or(-1))
14404                        .ok()
14405                        .filter(|&k| k < n)
14406                        .ok_or_else(|| {
14407                            Error::domain(
14408                                format!("{i} is not an index into {n} item(s)"),
14409                                span,
14410                            )
14411                        })?
14412                }
14413                None => {
14414                    let k = usize::try_from(i)
14415                        .map_err(|_| Error::domain(format!("{i} is not an index"), span))?;
14416                    crate::limits::count(k as u128 + 1, span)?;
14417                    k
14418                }
14419            };
14420            top = top.max(k + 1);
14421            cycle.push(k);
14422        }
14423        cycles.push(cycle);
14424    }
14425    let mut perm: Vec<usize> = (0..top).collect();
14426    for cycle in &cycles {
14427        for w in 0..cycle.len() {
14428            // Cycle (a b c) sends a's slot to b's item, b's to c's, c's to a's.
14429            perm[cycle[w]] = cycle[(w + 1) % cycle.len()];
14430        }
14431    }
14432    Ok(perm)
14433}
14434
14435/// `x C. y`: y's items permuted by x. A boxed x holds cycles; a numeric x
14436/// is a direct permutation of y's items, abbreviated where it is shorter
14437/// than y — the items it never names come first, in ascending order. An
14438/// atom is such a list of one, so `2 C. i.5` and `3 4 2 C. i.5` are the
14439/// same permutation.
14440fn permute(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14441    let ys = as_list(y);
14442    let n = ys.items();
14443    if x.dtype() != DType::Box {
14444        let perm = direct_permutation_of(&as_list(x), n, near, span)?;
14445        return Ok(select_items(&ys, &perm));
14446    }
14447    let mut perm = cycles_to_direct(x, Some(n), near, span)?;
14448    // Cycles name only what moves: everything else stays put.
14449    perm.extend(perm.len()..n);
14450    Ok(select_items(&ys, &perm))
14451}
14452
14453// ------------------------------------------------------- text and structure
14454
14455/// `u: y` and `⎕UCS`: characters and their codepoints. `pass_chars` is J's
14456/// monad, which answers characters with themselves; APL's `⎕UCS` converts
14457/// in both directions.
14458fn unicode(y: &Array, pass_chars: bool, near: NearInt, span: Span) -> Result<Array> {
14459    if y.dtype() == DType::Char {
14460        if pass_chars {
14461            return Ok(y.clone());
14462        }
14463        return Ok(chars_to_codes(y));
14464    }
14465    codes_to_chars(y, near, span)
14466}
14467
14468fn chars_to_codes(y: &Array) -> Array {
14469    let Data::Char(v) = &y.data else { return y.clone() };
14470    Array::new(y.shape.clone(), Data::I64(v.iter().map(|&c| c as i64).collect()))
14471}
14472
14473fn codes_to_chars(y: &Array, near: NearInt, span: Span) -> Result<Array> {
14474    let v = y
14475        .to_i64_vec_near(near)
14476        .ok_or_else(|| Error::domain("a codepoint must be an integer", span))?;
14477    let mut out = Vec::with_capacity(v.len());
14478    for &c in &v {
14479        let ch = u32::try_from(c).ok().and_then(char::from_u32).ok_or_else(|| {
14480            Error::domain(format!("{c} is not a Unicode codepoint"), span)
14481        })?;
14482        out.push(ch);
14483    }
14484    Ok(Array::new(y.shape.clone(), Data::Char(out.into())))
14485}
14486
14487/// `x u: y`: 3 asks for codepoints, 10 for the characters they name. The
14488/// other forms J defines are byte-oriented and are named, not guessed at.
14489fn unicode_form(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14490    let form = x
14491        .to_i64_vec()
14492        .ok_or_else(|| Error::domain("a conversion form is an integer", span))?
14493        .first()
14494        .copied()
14495        .unwrap_or(0);
14496    match form {
14497        3 if y.dtype() == DType::Char => Ok(chars_to_codes(y)),
14498        3 => Err(Error::domain("form 3 converts characters to codepoints", span)),
14499        10 => codes_to_chars(y, near, span),
14500        n => Err(Error::not_yet(format!("the byte-oriented unicode form ({n} u:)"), span)),
14501    }
14502}
14503
14504/// `s: y`: the argument's text, interned.
14505///
14506/// A character list carries its own delimiter in its first position, so
14507/// the two names of a list that begins with a backtick are what stands
14508/// between the backticks, and `s: 'a b'` is the one name `" b"`; the empty
14509/// list has no delimiter and no names. A character table gives one name per
14510/// row, trailing blanks trimmed, and its leading axes are the result's
14511/// shape. A boxed argument gives one name per box, the characters taken
14512/// exactly as they stand — a box is where a name with a trailing blank
14513/// comes from.
14514fn to_symbols(y: &Array, span: Span) -> Result<Array> {
14515    if let Some(boxes) = y.as_boxes() {
14516        let mut ids = Vec::with_capacity(boxes.len());
14517        for b in boxes {
14518            if b.rank() > 1 {
14519                return Err(Error::new(
14520                    ErrorKind::Rank,
14521                    "a boxed symbol name is a character list",
14522                    Some(span),
14523                ));
14524            }
14525            let row_major = b.to_row_major();
14526            let Data::Char(v) = &row_major.data else {
14527                if b.count() == 0 {
14528                    ids.push(crate::symbol::EMPTY);
14529                    continue;
14530                }
14531                return Err(Error::domain("a symbol is made from characters", span));
14532            };
14533            ids.push(crate::symbol::intern(&v.as_slice().iter().collect::<String>()));
14534        }
14535        return Ok(Array::new(y.shape.clone(), Data::Symbol(ids.into())));
14536    }
14537    let row_major = y.to_row_major();
14538    let Data::Char(v) = &row_major.data else {
14539        return Err(Error::domain(
14540            format!("s: makes symbols from characters, not {} data", y.dtype().name()),
14541            span,
14542        ));
14543    };
14544    let chars = v.as_slice();
14545    if y.rank() >= 2 {
14546        let width = y.shape[y.rank() - 1];
14547        let mut ids = Vec::with_capacity(chars.len() / width.max(1));
14548        for row in chars.chunks(width) {
14549            let name: String = row.iter().collect();
14550            ids.push(crate::symbol::intern(name.trim_end_matches(' ')));
14551        }
14552        return Ok(Array::new(y.shape[..y.rank() - 1].to_vec(), Data::Symbol(ids.into())));
14553    }
14554    let Some((&delim, rest)) = chars.split_first() else {
14555        return Ok(Array::new(vec![0], Data::empty(DType::Symbol)));
14556    };
14557    let mut ids = Vec::new();
14558    let mut name = String::new();
14559    for &c in rest {
14560        if c == delim {
14561            ids.push(crate::symbol::intern(&name));
14562            name.clear();
14563        } else {
14564            name.push(c);
14565        }
14566    }
14567    ids.push(crate::symbol::intern(&name));
14568    Ok(Array::new(vec![ids.len()], Data::Symbol(ids.into())))
14569}
14570
14571/// `x s: y`: the numbered symbol forms. 4 lays the names out as a character
14572/// table, blank-padded to the longest, and 5 boxes them one apiece. The
14573/// remaining numbers J defines report on its own symbol table — how many
14574/// slots it holds, which are in use, how it hashes them — and describe an
14575/// interpreter's internals rather than the language.
14576fn symbol_form(x: &Array, y: &Array, span: Span) -> Result<Array> {
14577    let form = x
14578        .to_i64_vec()
14579        .ok_or_else(|| Error::domain("a symbol form is an integer", span))?
14580        .first()
14581        .copied()
14582        .unwrap_or(0);
14583    if !matches!(form, 4 | 5) {
14584        return Err(Error::not_yet(format!("the symbol-table form ({form} s:)"), span));
14585    }
14586    let row_major = y.to_row_major();
14587    let Data::Symbol(ids) = &row_major.data else {
14588        return Err(Error::domain(
14589            format!("{form} s: reads symbols, not {} data", y.dtype().name()),
14590            span,
14591        ));
14592    };
14593    let names = crate::symbol::names(ids.as_slice());
14594    if form == 5 {
14595        let boxes: Vec<Array> =
14596            names.iter().map(|n| Array::from_chars(n.chars().collect())).collect();
14597        return Ok(Array::new(y.shape.clone(), Data::Box(boxes.into())));
14598    }
14599    let width = names.iter().map(|n| n.chars().count()).max().unwrap_or(0);
14600    let mut out: Vec<char> = Vec::with_capacity(names.len() * width);
14601    for n in &names {
14602        out.extend(n.chars());
14603        out.resize(out.len() + width - n.chars().count(), ' ');
14604    }
14605    let mut shape = y.shape.clone();
14606    shape.push(width);
14607    Ok(Array::new(shape, Data::Char(out.into())))
14608}
14609
14610/// `x $. y`: the numbered sparse forms.
14611///
14612/// `0` moves between the two storage kinds in whichever direction the
14613/// argument is not already in, and `1` builds a new sparse array from a
14614/// shape. The rest ask about a sparse argument: `_1` its shape, sparse axes
14615/// and sparse element boxed, `2` the sparse axes, `3` the sparse element,
14616/// `4` the stored index rows, `5` the stored cells, `7` how many entries
14617/// are stored, and `8` the same array with the entries that hold the sparse
14618/// element dropped. `2` also answers a dense argument, which has all of its
14619/// axes conceptually sparse; the others refuse one.
14620fn sparse_form(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14621    if x.rank() != 0 {
14622        return Err(Error::new(ErrorKind::Rank, "a sparse form is one atom", Some(span)));
14623    }
14624    let form = x
14625        .to_i64_vec_near(near)
14626        .and_then(|v| v.first().copied())
14627        .ok_or_else(|| Error::domain("a sparse form is an integer", span))?;
14628    match form {
14629        0 if y.is_sparse() => return Ok(y.densified()),
14630        0 => return crate::sparse::sparsify(y, span),
14631        1 => return crate::sparse::create(y, span),
14632        2 => {
14633            let axes: Vec<i64> = match y.sparse_parts() {
14634                Some(s) => s.axes.iter().map(|&k| k as i64).collect(),
14635                None => (0..y.rank() as i64).collect(),
14636            };
14637            return Ok(Array::from_i64(axes));
14638        }
14639        _ => {}
14640    }
14641    let Some(s) = y.sparse_parts() else {
14642        return Err(Error::domain(
14643            format!("{form} $. reads a sparse array, and this one is dense"),
14644            span,
14645        ));
14646    };
14647    match form {
14648        -1 => Ok(crate::sparse::attributes(y, s)),
14649        3 => Ok(crate::sparse::fill_of(s)),
14650        4 => Ok(crate::sparse::indices_of(s)),
14651        5 => Ok(crate::sparse::values_of(y, s)),
14652        7 => Ok(Array::scalar_i64(s.entries as i64)),
14653        8 => Ok(crate::sparse::compress(y, s)),
14654        _ => Err(Error::domain(format!("{form} is not a sparse form"), span)),
14655    }
14656}
14657
14658/// `L. y`: how deep the boxing goes. Anything unboxed is level 0.
14659fn boxing_level(y: &Array) -> i64 {
14660    match y.as_boxes() {
14661        None => 0,
14662        Some(bs) => 1 + bs.iter().map(boxing_level).max().unwrap_or(0),
14663    }
14664}
14665
14666/// `↓ y`: split — the vectors along the last axis, each enclosed, laid out
14667/// in the shape the remaining axes give. GNU APL has no monadic `↓`; this
14668/// follows Dyalog's published definition.
14669fn split_items(y: &Array) -> Array {
14670    if y.rank() == 0 {
14671        return Array::boxed(y.clone());
14672    }
14673    let last = y.shape[y.rank() - 1];
14674    let outer: Vec<usize> = y.shape[..y.rank() - 1].to_vec();
14675    let n: usize = outer.iter().product();
14676    let mut boxes = Vec::with_capacity(n);
14677    for i in 0..n {
14678        let mut data = Data::empty(y.dtype());
14679        for k in 0..last {
14680            push_elem(&mut data, &y.data, i * last + k);
14681        }
14682        boxes.push(Array::new(vec![last], data));
14683    }
14684    Array::new(outer, Data::Box(boxes.into()))
14685}
14686
14687/// `x ⊃ y`: pick. Each item of x is one step of a path — a boxed step is a
14688/// whole coordinate vector, a simple one indexes the items.
14689fn pick(x: &Array, y: &Array, origin: i64, near: NearInt, span: Span) -> Result<Array> {
14690    let xs = as_list(x);
14691    let mut cur = y.clone();
14692    for i in 0..xs.items() {
14693        let step = open_cell(&item_or_self(&xs, i));
14694        let idx = step
14695            .to_i64_vec_near(near)
14696            .ok_or_else(|| Error::domain("a pick path holds integers", span))?;
14697        let base =
14698            if cur.rank() == 0 { Array::new(vec![1], cur.data.clone()) } else { cur.clone() };
14699        if idx.len() > base.rank() {
14700            return Err(Error::new(
14701                ErrorKind::Length,
14702                format!(
14703                    "a path step of {} index(es) into a value of rank {}",
14704                    idx.len(),
14705                    cur.rank()
14706                ),
14707                Some(span),
14708            ));
14709        }
14710        let zeroed: Vec<i64> = idx.iter().map(|&v| v - origin).collect();
14711        let at = cell_index(&base, &zeroed, span)?;
14712        cur = open_cell(&base.cell_at(idx.len(), at));
14713    }
14714    Ok(cur)
14715}
14716
14717// ------------------------------------------------------------------ primes
14718
14719/// `x p: y`: the facts about primes J spells with this conjunction of
14720/// arguments. Every form here reads one integer and answers about it.
14721fn prime_meta(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14722    let form = one_int(x, "a prime query", near, span)?;
14723    let n = one_int(y, "a prime query", near, span)?;
14724    match form {
14725        // How many primes are below y.
14726        -1 => Ok(Array::scalar_i64(primes_below(n, span)?)),
14727        // Whether y is prime, and its negation.
14728        0 => Ok(Array::scalar_bool(!is_prime(n))),
14729        1 => Ok(Array::scalar_bool(is_prime(n))),
14730        // The factorisation as a table, and its top row on its own.
14731        2 | 3 => {
14732            let (ps, es) = factor_table(n, span)?;
14733            let k = ps.len();
14734            if form == 3 {
14735                return Ok(Array::from_i64(ps));
14736            }
14737            let mut all = ps;
14738            all.extend(es);
14739            Ok(Array::new(vec![2, k], Data::I64(all.into())))
14740        }
14741        // The neighbouring primes.
14742        4 => Ok(Array::scalar_i64(next_prime(n, span)?)),
14743        -4 => Ok(Array::scalar_i64(previous_prime(n, span)?)),
14744        other => Err(Error::domain(format!("{other} is not a prime query"), span)),
14745    }
14746}
14747
14748/// `x q: y`: the exponents of the primes in y — of the first x of them, or,
14749/// for `__`, of the ones that actually divide y over a second row.
14750fn prime_exponents(x: &Array, y: &Array, near: NearInt, span: Span) -> Result<Array> {
14751    let n = one_int(y, "prime exponents", near, span)?;
14752    let count = x.to_f64_vec().and_then(|v| v.first().copied()).unwrap_or(0.0);
14753    let (ps, es) = factor_table(n, span)?;
14754    if count == f64::NEG_INFINITY {
14755        let k = ps.len();
14756        let mut all = ps;
14757        all.extend(es);
14758        return Ok(Array::new(vec![2, k], Data::I64(all.into())));
14759    }
14760    let want = one_int(x, "prime exponents", near, span)?;
14761    if want < 0 {
14762        return Err(Error::not_yet(format!("the prime exponent form ({want} q:)"), span));
14763    }
14764    let mut out = Vec::with_capacity(want as usize);
14765    for i in 0..want {
14766        let p = nth_prime(i, span)?;
14767        out.push(ps.iter().position(|&q| q == p).map_or(0, |at| es[at]));
14768    }
14769    Ok(Array::from_i64(out))
14770}
14771
14772/// y's distinct prime factors, ascending, and how often each divides it.
14773fn factor_table(n: i64, span: Span) -> Result<(Vec<i64>, Vec<i64>)> {
14774    let factors = prime_factors(n, span)?;
14775    let mut ps: Vec<i64> = Vec::new();
14776    let mut es: Vec<i64> = Vec::new();
14777    for f in factors {
14778        if ps.last() == Some(&f) {
14779            *es.last_mut().unwrap() += 1;
14780        } else {
14781            ps.push(f);
14782            es.push(1);
14783        }
14784    }
14785    Ok((ps, es))
14786}
14787
14788fn is_prime(n: i64) -> bool {
14789    if n < 2 {
14790        return false;
14791    }
14792    let mut d = 2i64;
14793    while d.saturating_mul(d) <= n {
14794        if n % d == 0 {
14795            return false;
14796        }
14797        d += 1;
14798    }
14799    true
14800}
14801
14802fn primes_below(n: i64, span: Span) -> Result<i64> {
14803    if n < 0 {
14804        return Err(Error::domain("counting the primes below a negative number", span));
14805    }
14806    Ok((2..n).filter(|&k| is_prime(k)).count() as i64)
14807}
14808
14809fn next_prime(n: i64, span: Span) -> Result<i64> {
14810    let mut k = n.checked_add(1).ok_or_else(|| Error::domain("no next prime", span))?;
14811    while !is_prime(k) {
14812        k = k.checked_add(1).ok_or_else(|| Error::domain("no next prime", span))?;
14813    }
14814    Ok(k)
14815}
14816
14817fn previous_prime(n: i64, span: Span) -> Result<i64> {
14818    let mut k = n - 1;
14819    while k >= 2 {
14820        if is_prime(k) {
14821            return Ok(k);
14822        }
14823        k -= 1;
14824    }
14825    Err(Error::domain(format!("there is no prime below {n}"), span))
14826}
14827
14828/// One whole number from an argument that has to hold exactly that.
14829fn one_int(a: &Array, what: &str, near: NearInt, span: Span) -> Result<i64> {
14830    a.to_i64_vec_near(near)
14831        .and_then(|v| v.first().copied())
14832        .ok_or_else(|| Error::domain(format!("{what} needs an integer"), span))
14833}
14834
14835/// `x \\ y`: expand. Every 1 in x takes the next item of y; every 0 leaves
14836/// a fill in its place — the type's own fill, or, for a nested argument in
14837/// APL, the prototype of its first item.
14838fn expand(x: &Array, y: &Array, apl: bool, near: NearInt, span: Span) -> Result<Array> {
14839    let mask = x
14840        .to_i64_vec_near(near)
14841        .ok_or_else(|| Error::domain("an expansion mask holds 0s and 1s", span))?;
14842    if mask.iter().any(|&b| b != 0 && b != 1) {
14843        return Err(Error::domain("an expansion mask holds 0s and 1s", span));
14844    }
14845    let ys = as_list(y);
14846    let taken = mask.iter().filter(|&&b| b == 1).count();
14847    let n = ys.items();
14848    // A one-item argument spreads over every slot the mask opens.
14849    let spread = n == 1 && taken != 1;
14850    if !spread && taken != n {
14851        return Err(Error::new(
14852            ErrorKind::Length,
14853            format!("an expansion mask taking {taken} item(s) over {n}"),
14854            Some(span),
14855        ));
14856    }
14857    let m = ys.item_size();
14858    let fill = if apl { prototype_of(&ys) } else { None };
14859    let mut data = Data::empty(ys.dtype());
14860    let mut at = 0usize;
14861    for &b in &mask {
14862        if b == 1 {
14863            let from = if spread { 0 } else { at };
14864            for k in 0..m {
14865                push_elem(&mut data, &ys.data, from * m + k);
14866            }
14867            at += 1;
14868        } else {
14869            for _ in 0..m {
14870                push_gap(&mut data, &fill);
14871            }
14872        }
14873    }
14874    let mut shape = ys.shape.clone();
14875    if shape.is_empty() {
14876        shape.push(mask.len());
14877    } else {
14878        shape[0] = mask.len();
14879    }
14880    Ok(keep_proto(Array::new(shape, data), &ys, apl))
14881}
14882
14883/// `". y` and `⍎ y`: the characters of y as a program of this language,
14884/// compiled now and run here.
14885///
14886/// The nested program shares the caller's names and its output sink, which
14887/// is what makes `". 'a =. 3'` assign in the scope the sentence stands in.
14888/// It reaches nothing the caller could not reach: the sandbox contract is
14889/// about what a primitive may touch, and evaluation touches nothing new.
14890fn execute(y: &Array, apl: bool, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
14891    // An argument with no elements is the empty program, whatever type it
14892    // was going to hold — there is no character in it to refuse. J answers
14893    // the empty program with an empty value; APL's answers nothing at all,
14894    // which every caller of a verb here has to report as a refusal.
14895    if y.count() == 0 {
14896        if apl {
14897            return execute_source("", apl, ctx, span);
14898        }
14899        return Ok(Array::new(y.shape.clone(), Data::empty(DType::Bool)));
14900    }
14901    let Data::Char(v) = &y.data else {
14902        return Err(Error::domain("execute reads a character list", span));
14903    };
14904    let src: String = v.iter().collect();
14905    execute_source(&src, apl, ctx, span)
14906}
14907
14908/// [`execute`] over source that is already text: APL's `⎕` reads a line and
14909/// runs it, which is execute over a string nobody boxed into an array.
14910pub(crate) fn execute_source(
14911    src: &str,
14912    apl: bool,
14913    ctx: &mut Ctx<'_>,
14914    span: Span,
14915) -> Result<Array> {
14916    let lang = if apl { crate::Lang::Apl } else { crate::Lang::J };
14917    // The nested program runs under the dialect the caller was compiled
14918    // with — every setting of it, not the index origin alone.
14919    let dialect = ctx.cfg.rules.dialect();
14920    let nested = crate::compile(lang, src, &dialect).map_err(|e| nested_error(e, src, span))?;
14921    if !nested.params.is_empty() {
14922        return Err(Error::domain(
14923            "an executed string cannot take host data: `{name}` has nothing to bind to",
14924            span,
14925        ));
14926    }
14927    let mut rec = None;
14928    let (value, _) = crate::ir::run_block(&nested.stmts, None, ctx, &mut rec)
14929        .map_err(|e| nested_error(e, src, span))?;
14930    value.ok_or_else(|| Error::domain("the executed string yielded no value", span))
14931}
14932
14933/// The stream number a J file foreign was given, checked against the one
14934/// the sandbox opens for that direction.
14935///
14936/// J numbers its streams and its open files alike, so a number that is not
14937/// the standard one is a file handle; a boxed argument is a file NAME. Both
14938/// are the filesystem, which the sandbox closes.
14939fn stream_number(y: &Array, open: i64, what: &str, span: Span) -> Result<()> {
14940    let closed = || {
14941        Err(Error::sandbox(
14942            format!("{what} the standard stream {open} only; a file is outside the program"),
14943            span,
14944        ))
14945    };
14946    if matches!(y.data, Data::Box(_)) {
14947        return closed();
14948    }
14949    match y.to_i64_vec().as_deref() {
14950        Some([n]) if *n == open => Ok(()),
14951        Some([_]) => closed(),
14952        _ => Err(Error::domain(format!("{what} one stream number"), span)),
14953    }
14954}
14955
14956/// `3!:0 y`: the code J gives y's element type. The numbers are J's own,
14957/// and libjay's element types line up with them one for one.
14958/// J's code for the argument's element type. A sparse array has a code of
14959/// its own for every element type that can be stored sparsely, one factor
14960/// of 1024 above the dense one.
14961fn type_code(y: &Array) -> i64 {
14962    if y.is_sparse() {
14963        return 1024 * dense_type_code(y);
14964    }
14965    dense_type_code(y)
14966}
14967
14968fn dense_type_code(y: &Array) -> i64 {
14969    match y.dtype() {
14970        DType::Bool => 1,
14971        DType::Char => 2,
14972        DType::I64 => 4,
14973        DType::F64 => 8,
14974        DType::Complex => 16,
14975        DType::Box => 32,
14976        DType::Ext => 64,
14977        DType::Rat => 128,
14978        DType::Symbol => 65536,
14979    }
14980}
14981
14982/// An error from an executed string, re-pointed at the sentence that ran it.
14983/// The inner diagnostic still reads in full, as a note, because its spans
14984/// point into a source the caller never sees.
14985fn nested_error(e: Error, src: &str, span: Span) -> Error {
14986    let inner = e.render(src);
14987    let mut out = Error::new(e.kind, format!("in the executed string: {}", e.msg), Some(span));
14988    out.notes.push(inner.trim_end().to_string());
14989    out
14990}
14991
14992// ------------------------------------------------------------------- words
14993
14994/// `;: y`: J's own word rules over a character list, each word a box. A run
14995/// of numeric literals separated by blanks is one word, which is what makes
14996/// `'1 2 3'` a single number and `'i.5'` two words.
14997fn words(y: &Array, span: Span) -> Result<Array> {
14998    // Nothing to read is no word, whatever type the empty was going to
14999    // hold: `;: (0$1 2 3)` is the empty list of boxes.
15000    if y.count() == 0 {
15001        return Ok(Array::new(vec![0], Data::Box(Vec::new().into())));
15002    }
15003    let Data::Char(v) = &y.data else {
15004        return Err(Error::domain("words reads a character list", span));
15005    };
15006    let src: Vec<char> = v.as_slice().to_vec();
15007    let n = src.len();
15008    let mut out: Vec<Array> = Vec::new();
15009    let mut i = 0usize;
15010    let numeric_start = |k: usize| -> bool {
15011        k < n && (src[k].is_ascii_digit() || src[k] == '_')
15012    };
15013    while i < n {
15014        let c = src[i];
15015        if c == ' ' || c == '\t' {
15016            i += 1;
15017            continue;
15018        }
15019        let start = i;
15020        if c == '\'' {
15021            i += 1;
15022            loop {
15023                if i >= n {
15024                    return Err(Error::parse("a word list ends inside a string", span));
15025                }
15026                if src[i] == '\'' {
15027                    i += 1;
15028                    if i < n && src[i] == '\'' {
15029                        i += 1;
15030                        continue;
15031                    }
15032                    break;
15033                }
15034                i += 1;
15035            }
15036        } else if c.is_ascii_alphabetic() {
15037            while i < n && (src[i].is_ascii_alphanumeric() || src[i] == '_') {
15038                i += 1;
15039            }
15040            if i < n && (src[i] == '.' || src[i] == ':') {
15041                i += 1;
15042            }
15043            // `NB.` swallows the rest of the line, comment and all.
15044            if src[start..i].iter().collect::<String>() == "NB." {
15045                while i < n && src[i] != '\n' {
15046                    i += 1;
15047                }
15048            }
15049        } else if numeric_start(i) {
15050            loop {
15051                while i < n && (src[i].is_ascii_alphanumeric() || src[i] == '.' || src[i] == '_')
15052                {
15053                    i += 1;
15054                }
15055                // A blank between two numeric literals keeps one word.
15056                let mut j = i;
15057                while j < n && src[j] == ' ' {
15058                    j += 1;
15059                }
15060                if j > i && numeric_start(j) {
15061                    i = j;
15062                    continue;
15063                }
15064                break;
15065            }
15066        } else {
15067            i += 1;
15068            while i < n && (src[i] == '.' || src[i] == ':') {
15069                i += 1;
15070            }
15071        }
15072        out.push(Array::from_chars(src[start..i].to_vec()));
15073    }
15074    let k = out.len();
15075    Ok(Array::new(vec![k], Data::Box(out.into())))
15076}
15077
15078#[cfg(test)]
15079mod tests {
15080    use super::*;
15081
15082    /// A context bound to a discarding output sink.
15083    macro_rules! ctx {
15084        ($name:ident, $agreement:expr) => {
15085            let mut sink = |_: &str| {};
15086            let mut env = Env::new(Vec::new());
15087            #[allow(unused_mut)]
15088            let mut $name = Ctx {
15089                cfg: EvalCfg {
15090                    agreement: $agreement,
15091                    fmt: FmtOpts::J,
15092                    tol: Tol::J,
15093                    // The agreement names the language here, so the rules
15094                    // a verb reads are that language's shipped dialect.
15095                    rules: crate::frontend::Dialect::default()
15096                        .rules(if $agreement == Agreement::ExactOrScalar {
15097                            crate::Lang::Apl
15098                        } else {
15099                            crate::Lang::J
15100                        })
15101                        .expect("the shipped dialect is implemented"),
15102                },
15103                out: &mut sink,
15104                inp: None,
15105                env: &mut env,
15106                device: None,
15107                shy: false,
15108            };
15109        };
15110        ($name:ident) => {
15111            ctx!($name, Agreement::LeadingPrefix);
15112        };
15113    }
15114
15115    fn scalar_prim(name: &'static str, monad: MonadOp, dyad: DyadOp) -> Verb {
15116        Verb::Prim(Prim { name, monad, dyad, ranks: [0, 0, 0] })
15117    }
15118
15119    fn inf_prim(name: &'static str, monad: MonadOp, dyad: DyadOp) -> Verb {
15120        Verb::Prim(Prim { name, monad, dyad, ranks: [RANK_INF, RANK_INF, RANK_INF] })
15121    }
15122
15123    fn plus() -> Verb {
15124        scalar_prim("+", MonadOp::Scalar(ScalarMonad::Conj), DyadOp::Scalar(ScalarDyad::Add))
15125    }
15126    fn minus() -> Verb {
15127        scalar_prim("-", MonadOp::Scalar(ScalarMonad::Neg), DyadOp::Scalar(ScalarDyad::Sub))
15128    }
15129    fn times() -> Verb {
15130        scalar_prim("*", MonadOp::Scalar(ScalarMonad::Signum), DyadOp::Scalar(ScalarDyad::Mul))
15131    }
15132    fn pct() -> Verb {
15133        scalar_prim("%", MonadOp::Scalar(ScalarMonad::Recip), DyadOp::Scalar(ScalarDyad::DivJ))
15134    }
15135    fn div_apl() -> Verb {
15136        scalar_prim("÷", MonadOp::Scalar(ScalarMonad::Recip), DyadOp::Scalar(ScalarDyad::DivApl))
15137    }
15138    fn floor_v() -> Verb {
15139        scalar_prim("<.", MonadOp::Scalar(ScalarMonad::Floor), DyadOp::Scalar(ScalarDyad::Min))
15140    }
15141    fn ceil_v() -> Verb {
15142        scalar_prim(">.", MonadOp::Scalar(ScalarMonad::Ceil), DyadOp::Scalar(ScalarDyad::Max))
15143    }
15144    fn pow_v() -> Verb {
15145        scalar_prim("^", MonadOp::Scalar(ScalarMonad::Exp), DyadOp::Scalar(ScalarDyad::Pow))
15146    }
15147    fn residue_v() -> Verb {
15148        scalar_prim("|", MonadOp::Scalar(ScalarMonad::Abs), DyadOp::Scalar(ScalarDyad::Residue))
15149    }
15150    fn eq_v() -> Verb {
15151        scalar_prim("=", MonadOp::None, DyadOp::Scalar(ScalarDyad::Eq))
15152    }
15153    fn lt_v() -> Verb {
15154        scalar_prim("<", MonadOp::None, DyadOp::Scalar(ScalarDyad::Lt))
15155    }
15156    fn not_v() -> Verb {
15157        scalar_prim("-.", MonadOp::Scalar(ScalarMonad::Not), DyadOp::None)
15158    }
15159    fn sqrt_v() -> Verb {
15160        scalar_prim("%:", MonadOp::Scalar(ScalarMonad::Sqrt), DyadOp::NotYet("dyadic root"))
15161    }
15162    fn dollar() -> Verb {
15163        inf_prim("$", MonadOp::ShapeOf, DyadOp::Reshape)
15164    }
15165    fn pound() -> Verb {
15166        inf_prim("#", MonadOp::Tally, DyadOp::NotYet("copy"))
15167    }
15168    fn comma() -> Verb {
15169        inf_prim(",", MonadOp::Ravel, DyadOp::NotYet("append"))
15170    }
15171    fn transpose_v() -> Verb {
15172        inf_prim("|:", MonadOp::TransposeAxes, DyadOp::NotYet("dyadic transpose"))
15173    }
15174    fn head_v() -> Verb {
15175        inf_prim("{.", MonadOp::Head, DyadOp::Take)
15176    }
15177    fn behead_v() -> Verb {
15178        inf_prim("}.", MonadOp::Behead, DyadOp::Drop)
15179    }
15180    fn iota() -> Verb {
15181        inf_prim("i.", MonadOp::IotaJ, DyadOp::NotYet("index of"))
15182    }
15183    fn iota_apl(origin: i64) -> Verb {
15184        inf_prim("⍳", MonadOp::IotaApl { origin }, DyadOp::NotYet("index of"))
15185    }
15186    fn right_v() -> Verb {
15187        inf_prim("]", MonadOp::Same, DyadOp::Right)
15188    }
15189    fn echo_v() -> Verb {
15190        inf_prim("echo", MonadOp::Echo, DyadOp::None)
15191    }
15192
15193    fn b(v: Verb) -> Box<Verb> {
15194        Box::new(v)
15195    }
15196
15197    fn mat(rows: usize, cols: usize, v: Vec<i64>) -> Array {
15198        Array::new(vec![rows, cols], Data::I64(v.into()))
15199    }
15200
15201    /// The elements in reading order, whatever layout the result kept.
15202    fn ints(a: &Array) -> Vec<i64> {
15203        a.to_row_major().as_i64_slice().expect("integer result").to_vec()
15204    }
15205
15206    fn floats(a: &Array) -> Vec<f64> {
15207        a.to_row_major().as_f64_slice().expect("float result").to_vec()
15208    }
15209
15210    fn bools(a: &Array) -> Vec<u8> {
15211        match &a.to_row_major().data {
15212            Data::Bool(v) => v.to_vec(),
15213            other => panic!("expected boolean result, got {other:?}"),
15214        }
15215    }
15216
15217    fn sp() -> Span {
15218        Span::new(0, 1)
15219    }
15220
15221    fn close(a: f64, b: f64) -> bool {
15222        (a - b).abs() < 1e-9 || (a.is_infinite() && b.is_infinite() && a.signum() == b.signum())
15223    }
15224
15225    // ------------------------------------------------------------- naming
15226
15227    #[test]
15228    fn names_of_primitives_and_derived_verbs() {
15229        assert_eq!(plus().name(), "+");
15230        assert_eq!(Verb::Rank(b(plus()), [1, 1, 1]).name(), "+\"1");
15231        assert_eq!(Verb::Rank(b(plus()), [0, 1, RANK_INF]).name(), "+\"0 1 _");
15232        assert_eq!(Verb::Rank(b(plus()), [RANK_INF; 3]).name(), "+\"_");
15233        assert_eq!(Verb::Reduce(b(plus())).name(), "+/");
15234        assert_eq!(Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]).name(), "+/\"1");
15235        assert_eq!(Verb::Fork(b(plus()), b(minus()), b(times())).name(), "(+ - *)");
15236        assert_eq!(
15237            Verb::NounFork(Array::scalar_i64(1), b(plus()), b(minus())).name(),
15238            "(n + -)"
15239        );
15240        assert_eq!(Verb::Hook(b(plus()), b(minus())).name(), "(+ -)");
15241        assert_eq!(Verb::Atop(b(plus()), b(minus())).name(), "(+@:-)");
15242        assert_eq!(Verb::Compose(b(plus()), b(minus())).name(), "(+&:-)");
15243        assert_eq!(Verb::BondLeft(Array::scalar_i64(1), b(plus())).name(), "(n&+)");
15244        assert_eq!(Verb::BondRight(b(plus()), Array::scalar_i64(1)).name(), "(+&n)");
15245    }
15246
15247    #[test]
15248    fn composition_applies_the_right_verb_to_both_arguments() {
15249        ctx!(c);
15250        let v = Verb::Compose(b(plus()), b(times()));
15251        // Monadically an atop; dyadically the right verb runs on each side.
15252        let r = v.monad(&Array::from_i64(vec![-2, 0, 3]), &mut c, sp()).unwrap();
15253        assert_eq!(ints(&r), vec![-1, 0, 1]);
15254        let r = v
15255            .dyad(&Array::scalar_i64(-5), &Array::scalar_i64(7), &mut c, sp())
15256            .unwrap();
15257        assert_eq!(ints(&r), vec![0]);
15258        // A bond has a monadic valence only.
15259        let bond = Verb::BondLeft(Array::scalar_i64(10), b(minus()));
15260        let r = bond.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
15261        assert_eq!(ints(&r), vec![9, 8]);
15262        let e = bond
15263            .dyad(&Array::scalar_i64(1), &Array::scalar_i64(2), &mut c, sp())
15264            .unwrap_err();
15265        assert_eq!(e.kind, ErrorKind::Domain);
15266        let bond = Verb::BondRight(b(minus()), Array::scalar_i64(10));
15267        let r = bond.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
15268        assert_eq!(ints(&r), vec![-9, -8]);
15269    }
15270
15271    // ------------------------------------------------- rank and agreement
15272
15273    #[test]
15274    fn scalar_monad_covers_the_whole_buffer() {
15275        ctx!(c);
15276        let r = minus().monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
15277        assert_eq!(r.shape, vec![2, 3]);
15278        assert_eq!(ints(&r), vec![-1, -2, -3, -4, -5, -6]);
15279    }
15280
15281    #[test]
15282    fn leading_prefix_agreement_broadcasts_per_row() {
15283        ctx!(c);
15284        let x = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15285        let y = Array::from_i64(vec![10, 20]);
15286        let r = plus().dyad(&x, &y, &mut c, sp()).unwrap();
15287        assert_eq!(r.shape, vec![2, 3]);
15288        assert_eq!(ints(&r), vec![11, 12, 13, 24, 25, 26]);
15289        // and the same pairing with the operands swapped
15290        let r = plus().dyad(&y, &x, &mut c, sp()).unwrap();
15291        assert_eq!(ints(&r), vec![11, 12, 13, 24, 25, 26]);
15292    }
15293
15294    #[test]
15295    fn exact_or_scalar_rejects_a_prefix_frame() {
15296        ctx!(c, Agreement::ExactOrScalar);
15297        let x = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15298        let y = Array::from_i64(vec![10, 20]);
15299        let e = plus().dyad(&x, &y, &mut c, sp()).unwrap_err();
15300        assert_eq!(e.kind, ErrorKind::Shape);
15301        assert!(e.msg.contains("2 3"), "{}", e.msg);
15302        assert!(e.msg.contains("right shape 2"), "{}", e.msg);
15303    }
15304
15305    #[test]
15306    fn exact_or_scalar_accepts_equal_frames_and_scalars() {
15307        ctx!(c, Agreement::ExactOrScalar);
15308        let x = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15309        let r = plus().dyad(&x, &x, &mut c, sp()).unwrap();
15310        assert_eq!(ints(&r), vec![2, 4, 6, 8, 10, 12]);
15311        let r = plus().dyad(&Array::scalar_i64(10), &x, &mut c, sp()).unwrap();
15312        assert_eq!(r.shape, vec![2, 3]);
15313        assert_eq!(ints(&r), vec![11, 12, 13, 14, 15, 16]);
15314        let r = plus().dyad(&x, &Array::scalar_i64(10), &mut c, sp()).unwrap();
15315        assert_eq!(ints(&r), vec![11, 12, 13, 14, 15, 16]);
15316    }
15317
15318    #[test]
15319    fn vector_length_mismatch_is_a_length_error() {
15320        ctx!(c);
15321        let e = plus()
15322            .dyad(&Array::from_i64(vec![1, 2, 3]), &Array::from_i64(vec![1, 2, 3, 4, 5]), &mut c, sp())
15323            .unwrap_err();
15324        assert_eq!(e.kind, ErrorKind::Length);
15325        assert!(e.msg.contains("left shape 3"), "{}", e.msg);
15326        assert!(e.msg.contains("right shape 5"), "{}", e.msg);
15327        assert!(e.notes[0].contains("axis 0"), "{:?}", e.notes);
15328    }
15329
15330    #[test]
15331    fn diverging_matrix_frames_name_the_axis() {
15332        ctx!(c);
15333        let e = plus()
15334            .dyad(&mat(2, 3, vec![0; 6]), &mat(2, 4, vec![0; 8]), &mut c, sp())
15335            .unwrap_err();
15336        assert_eq!(e.kind, ErrorKind::Shape);
15337        assert!(e.notes[0].contains("axis 1"), "{:?}", e.notes);
15338    }
15339
15340    #[test]
15341    fn dyadic_rank_pairs_rows_with_the_whole_right_argument() {
15342        ctx!(c);
15343        // Left cells are rows, the right argument is one cell for all of them.
15344        let v = Verb::Rank(b(plus()), [0, 1, 1]);
15345        let r = v
15346            .dyad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &Array::from_i64(vec![10, 20, 30]), &mut c, sp())
15347            .unwrap();
15348        assert_eq!(r.shape, vec![2, 3]);
15349        assert_eq!(ints(&r), vec![11, 22, 33, 14, 25, 36]);
15350    }
15351
15352    #[test]
15353    fn surplus_frame_axes_repeat_the_shorter_frames_cells() {
15354        ctx!(c);
15355        // Left cells are scalars (frame 2 2), right cells are rows (frame 2):
15356        // each right row serves the two left cells sharing its index.
15357        let v = Verb::Rank(b(head_v()), [0, 0, 1]);
15358        let x = mat(2, 2, vec![1, 1, 2, 2]);
15359        let y = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15360        let r = v.dyad(&x, &y, &mut c, sp()).unwrap();
15361        assert_eq!(r.shape, vec![2, 2, 2]);
15362        assert_eq!(ints(&r), vec![1, 0, 1, 0, 4, 5, 4, 5]);
15363    }
15364
15365    #[test]
15366    fn an_empty_frame_pairs_its_single_cell_with_every_other_cell() {
15367        ctx!(c, Agreement::ExactOrScalar);
15368        // Right cell rank 1 leaves an empty right frame; the left frame is 2.
15369        let v = Verb::Rank(b(head_v()), [0, 0, 1]);
15370        let x = Array::from_i64(vec![1, 2]);
15371        let y = Array::from_i64(vec![7, 8, 9]);
15372        let r = v.dyad(&x, &y, &mut c, sp()).unwrap();
15373        assert_eq!(r.shape, vec![2, 2]);
15374        assert_eq!(ints(&r), vec![7, 0, 7, 8]);
15375    }
15376
15377    #[test]
15378    fn negative_rank_leaves_frame_axes() {
15379        ctx!(c);
15380        // Rank _1 on a matrix leaves one frame axis: shape of each row.
15381        let v = Verb::Rank(b(dollar()), [-1, -1, -1]);
15382        let r = v.monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
15383        assert_eq!(r.shape, vec![2, 1]);
15384        assert_eq!(ints(&r), vec![3, 3]);
15385    }
15386
15387    #[test]
15388    fn effective_rank_clamps_and_counts_back() {
15389        assert_eq!(effective_rank(0, 3), 0);
15390        assert_eq!(effective_rank(2, 1), 1);
15391        assert_eq!(effective_rank(RANK_INF, 4), 4);
15392        assert_eq!(effective_rank(-1, 3), 2);
15393        assert_eq!(effective_rank(-5, 3), 0);
15394    }
15395
15396    // ---------------------------------------------------------- reduction
15397
15398    #[test]
15399    fn reduction_folds_right_to_left() {
15400        ctx!(c);
15401        // -/ 1 2 3 is 1-(2-3), not (1-2)-3.
15402        let r = Verb::Reduce(b(minus()))
15403            .monad(&Array::from_i64(vec![1, 2, 3]), &mut c, sp())
15404            .unwrap();
15405        assert!(r.shape.is_empty());
15406        assert_eq!(ints(&r), vec![2]);
15407    }
15408
15409    #[test]
15410    fn reduction_of_one_item_and_of_a_scalar() {
15411        ctx!(c);
15412        let r = Verb::Reduce(b(plus()))
15413            .monad(&Array::from_i64(vec![7]), &mut c, sp())
15414            .unwrap();
15415        assert!(r.shape.is_empty());
15416        assert_eq!(ints(&r), vec![7]);
15417        let r = Verb::Reduce(b(plus())).monad(&Array::scalar_i64(7), &mut c, sp()).unwrap();
15418        assert_eq!(ints(&r), vec![7]);
15419    }
15420
15421    #[test]
15422    fn reduction_runs_along_the_leading_axis() {
15423        ctx!(c);
15424        let r = Verb::Reduce(b(plus()))
15425            .monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp())
15426            .unwrap();
15427        assert_eq!(r.shape, vec![3]);
15428        assert_eq!(ints(&r), vec![5, 7, 9]);
15429    }
15430
15431    #[test]
15432    fn rank_wrapped_reduction_sums_the_last_axis() {
15433        ctx!(c);
15434        let v = Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]);
15435        let r = v.monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
15436        assert_eq!(r.shape, vec![2]);
15437        assert_eq!(ints(&r), vec![6, 15]);
15438    }
15439
15440    #[test]
15441    fn empty_reduction_uses_the_identity_cell() {
15442        ctx!(c);
15443        let empty = Array::new(vec![0, 2], Data::I64(vec![].into()));
15444        let r = Verb::Reduce(b(plus())).monad(&empty, &mut c, sp()).unwrap();
15445        assert_eq!(r.shape, vec![2]);
15446        assert_eq!(ints(&r), vec![0, 0]);
15447        let r = Verb::Reduce(b(times())).monad(&empty, &mut c, sp()).unwrap();
15448        assert_eq!(ints(&r), vec![1, 1]);
15449        let r = Verb::Reduce(b(floor_v())).monad(&empty, &mut c, sp()).unwrap();
15450        assert!(floats(&r).iter().all(|&x| x == f64::INFINITY));
15451        let r = Verb::Reduce(b(ceil_v())).monad(&empty, &mut c, sp()).unwrap();
15452        assert!(floats(&r).iter().all(|&x| x == f64::NEG_INFINITY));
15453        // Subtraction and division have identities too, and a comparison
15454        // has the conventional one both references print.
15455        let r = Verb::Reduce(b(minus())).monad(&empty, &mut c, sp()).unwrap();
15456        assert_eq!(ints(&r), vec![0, 0]);
15457        let r = Verb::Reduce(b(pct())).monad(&empty, &mut c, sp()).unwrap();
15458        assert_eq!(ints(&r), vec![1, 1]);
15459        let r = Verb::Reduce(b(eq_v())).monad(&empty, &mut c, sp()).unwrap();
15460        assert_eq!(bools(&r), vec![1, 1]);
15461        // An empty vector reduces to a scalar identity.
15462        let r = Verb::Reduce(b(plus()))
15463            .monad(&Array::empty(DType::I64), &mut c, sp())
15464            .unwrap();
15465        assert!(r.shape.is_empty());
15466        assert_eq!(ints(&r), vec![0]);
15467    }
15468
15469    #[test]
15470    fn empty_reduction_without_an_identity_is_a_domain_error() {
15471        ctx!(c);
15472        // A derived verb has no identity cell at all; among the primitives
15473        // only the logarithm and the circle functions are left without one,
15474        // which is what both references do.
15475        let v = Verb::Hook(b(plus()), b(minus()));
15476        let e = Verb::Reduce(b(v)).monad(&Array::empty(DType::I64), &mut c, sp()).unwrap_err();
15477        assert_eq!(e.kind, ErrorKind::Domain);
15478        assert!(e.msg.contains("identity"), "{}", e.msg);
15479    }
15480
15481    #[test]
15482    fn reduction_with_a_non_primitive_verb_uses_the_general_fold() {
15483        ctx!(c);
15484        // The hook x (+ -) y is x + (-y), so this folds as 1-(2-3).
15485        let v = Verb::Reduce(b(Verb::Hook(b(plus()), b(minus()))));
15486        let r = v.monad(&Array::from_i64(vec![1, 2, 3]), &mut c, sp()).unwrap();
15487        assert_eq!(ints(&r), vec![2]);
15488    }
15489
15490    #[test]
15491    fn dyadic_reduction_is_the_table() {
15492        ctx!(c);
15493        // `x u/ y` is the table (outer product), not a windowed reduction —
15494        // the windows are `x u\ y`.
15495        let v = Verb::Reduce(b(plus()));
15496        let r = v
15497            .dyad(&Array::scalar_i64(2), &Array::from_i64(vec![1, 2, 3]), &mut c, sp())
15498            .unwrap();
15499        assert_eq!(r.shape, vec![3]);
15500        assert_eq!(ints(&r), vec![3, 4, 5]);
15501        // The cells are the ones the inner verb's ranks ask for, so a scalar
15502        // verb pairs every atom of x with every atom of y.
15503        let r = v
15504            .dyad(&Array::from_i64(vec![1, 2, 3]), &Array::from_i64(vec![10, 20]), &mut c, sp())
15505            .unwrap();
15506        assert_eq!(r.shape, vec![3, 2]);
15507        assert_eq!(ints(&r), vec![11, 21, 12, 22, 13, 23]);
15508        // An infinite-rank verb takes both arguments whole: one application.
15509        let cat = Verb::Reduce(b(inf_prim(",", MonadOp::Ravel, DyadOp::AppendLeading)));
15510        let r = cat
15511            .dyad(&Array::from_i64(vec![1, 2]), &Array::from_i64(vec![3, 4]), &mut c, sp())
15512            .unwrap();
15513        assert_eq!(r.shape, vec![4]);
15514        assert_eq!(ints(&r), vec![1, 2, 3, 4]);
15515    }
15516
15517    // --------------------------------------------------------- arithmetic
15518
15519    #[test]
15520    fn integer_overflow_promotes_the_whole_result_to_float() {
15521        ctx!(c);
15522        let r = plus()
15523            .dyad(&Array::from_i64(vec![1, i64::MAX]), &Array::scalar_i64(1), &mut c, sp())
15524            .unwrap();
15525        assert_eq!(r.dtype(), DType::F64);
15526        let v = floats(&r);
15527        assert!(close(v[0], 2.0));
15528        assert!(close(v[1], i64::MAX as f64 + 1.0));
15529        // Without overflow the result stays integral.
15530        let r = plus()
15531            .dyad(&Array::from_i64(vec![1, 2]), &Array::scalar_i64(1), &mut c, sp())
15532            .unwrap();
15533        assert_eq!(r.dtype(), DType::I64);
15534    }
15535
15536    #[test]
15537    fn reduction_overflow_promotes_too() {
15538        ctx!(c);
15539        let r = Verb::Reduce(b(plus()))
15540            .monad(&Array::from_i64(vec![i64::MAX, i64::MAX]), &mut c, sp())
15541            .unwrap();
15542        assert_eq!(r.dtype(), DType::F64);
15543        assert!(close(floats(&r)[0], 2.0 * i64::MAX as f64));
15544    }
15545
15546    #[test]
15547    fn booleans_widen_to_integers_in_arithmetic() {
15548        ctx!(c);
15549        let bits = Array::new(vec![3], Data::Bool(vec![1, 0, 1].into()));
15550        let r = plus().dyad(&bits, &bits, &mut c, sp()).unwrap();
15551        assert_eq!(r.dtype(), DType::I64);
15552        assert_eq!(ints(&r), vec![2, 0, 2]);
15553    }
15554
15555    #[test]
15556    fn j_division_is_float_and_survives_zero() {
15557        ctx!(c);
15558        let r = pct()
15559            .dyad(&Array::from_i64(vec![1, -1, 0, 6]), &Array::from_i64(vec![0, 0, 0, 4]), &mut c, sp())
15560            .unwrap();
15561        let v = floats(&r);
15562        assert_eq!(v[0], f64::INFINITY);
15563        assert_eq!(v[1], f64::NEG_INFINITY);
15564        assert_eq!(v[2], 0.0);
15565        assert!(close(v[3], 1.5));
15566    }
15567
15568    #[test]
15569    fn apl_division_by_zero_is_a_domain_error_except_zero_by_zero() {
15570        ctx!(c, Agreement::ExactOrScalar);
15571        let r = div_apl()
15572            .dyad(&Array::scalar_i64(0), &Array::scalar_i64(0), &mut c, sp())
15573            .unwrap();
15574        assert!(close(floats(&r)[0], 1.0));
15575        let e = div_apl()
15576            .dyad(&Array::scalar_i64(1), &Array::scalar_i64(0), &mut c, sp())
15577            .unwrap_err();
15578        assert_eq!(e.kind, ErrorKind::Domain);
15579        assert!(e.msg.contains("division by zero"), "{}", e.msg);
15580        let r = div_apl()
15581            .dyad(&Array::scalar_i64(6), &Array::scalar_i64(4), &mut c, sp())
15582            .unwrap();
15583        assert!(close(floats(&r)[0], 1.5));
15584    }
15585
15586    #[test]
15587    fn reciprocal_of_zero_is_infinite() {
15588        ctx!(c);
15589        let r = pct().monad(&Array::from_i64(vec![0, 2]), &mut c, sp()).unwrap();
15590        let v = floats(&r);
15591        assert_eq!(v[0], f64::INFINITY);
15592        assert!(close(v[1], 0.5));
15593    }
15594
15595    #[test]
15596    fn residue_takes_the_sign_of_the_left_argument() {
15597        ctx!(c);
15598        let x = Array::from_i64(vec![3, 3, -3, -3, 0]);
15599        let y = Array::from_i64(vec![5, -5, 5, -5, 5]);
15600        let r = residue_v().dyad(&x, &y, &mut c, sp()).unwrap();
15601        assert_eq!(ints(&r), vec![2, 1, -1, -2, 5]);
15602        // Floats use the same rule via the floor of the quotient.
15603        let r = residue_v()
15604            .dyad(&Array::from_f64(vec![2.5]), &Array::from_f64(vec![7.0]), &mut c, sp())
15605            .unwrap();
15606        assert!(close(floats(&r)[0], 2.0));
15607    }
15608
15609    #[test]
15610    fn power_stays_integral_when_it_can() {
15611        ctx!(c);
15612        let r = pow_v()
15613            .dyad(&Array::from_i64(vec![2, 0, 5]), &Array::from_i64(vec![10, 0, 1]), &mut c, sp())
15614            .unwrap();
15615        assert_eq!(r.dtype(), DType::I64);
15616        assert_eq!(ints(&r), vec![1024, 1, 5]);
15617        // A negative exponent forces the float path for the whole result.
15618        let r = pow_v()
15619            .dyad(&Array::from_i64(vec![2, 4]), &Array::from_i64(vec![-1, 2]), &mut c, sp())
15620            .unwrap();
15621        assert_eq!(r.dtype(), DType::F64);
15622        assert!(close(floats(&r)[0], 0.5));
15623        assert!(close(floats(&r)[1], 16.0));
15624        // Overflow does the same.
15625        let r = pow_v()
15626            .dyad(&Array::scalar_i64(10), &Array::scalar_i64(30), &mut c, sp())
15627            .unwrap();
15628        assert_eq!(r.dtype(), DType::F64);
15629    }
15630
15631    #[test]
15632    fn comparisons_yield_booleans() {
15633        ctx!(c);
15634        let r = lt_v()
15635            .dyad(&Array::from_i64(vec![1, 2, 3]), &Array::scalar_i64(2), &mut c, sp())
15636            .unwrap();
15637        assert_eq!(bools(&r), vec![1, 0, 0]);
15638        let r = eq_v()
15639            .dyad(&Array::from_f64(vec![1.0, 2.0]), &Array::from_i64(vec![1, 3]), &mut c, sp())
15640            .unwrap();
15641        assert_eq!(bools(&r), vec![1, 0]);
15642    }
15643
15644    #[test]
15645    fn characters_compare_but_do_not_add() {
15646        ctx!(c);
15647        let a = Array::from_chars(vec!['a', 'b']);
15648        let bb = Array::from_chars(vec!['a', 'c']);
15649        assert_eq!(bools(&eq_v().dyad(&a, &bb, &mut c, sp()).unwrap()), vec![1, 0]);
15650        let e = plus().dyad(&a, &bb, &mut c, sp()).unwrap_err();
15651        assert_eq!(e.kind, ErrorKind::Type);
15652        assert!(e.msg.contains("characters"), "{}", e.msg);
15653        let e = lt_v().dyad(&a, &bb, &mut c, sp()).unwrap_err();
15654        assert_eq!(e.kind, ErrorKind::Type);
15655        let e = plus().dyad(&a, &Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap_err();
15656        assert_eq!(e.kind, ErrorKind::Type);
15657        assert!(e.msg.contains("character"), "{}", e.msg);
15658        let e = plus().monad(&a, &mut c, sp()).unwrap_err();
15659        assert_eq!(e.kind, ErrorKind::Type);
15660    }
15661
15662    #[test]
15663    fn floor_and_ceiling_return_integers_when_they_fit() {
15664        ctx!(c);
15665        let r = floor_v().monad(&Array::from_f64(vec![1.5, -1.5]), &mut c, sp()).unwrap();
15666        assert_eq!(r.dtype(), DType::I64);
15667        assert_eq!(ints(&r), vec![1, -2]);
15668        let r = ceil_v().monad(&Array::from_f64(vec![1.5, -1.5]), &mut c, sp()).unwrap();
15669        assert_eq!(ints(&r), vec![2, -1]);
15670        // Values outside the integer range stay floating.
15671        let r = floor_v().monad(&Array::from_f64(vec![1e30]), &mut c, sp()).unwrap();
15672        assert_eq!(r.dtype(), DType::F64);
15673        // Integers pass through unchanged.
15674        let r = floor_v().monad(&Array::from_i64(vec![3]), &mut c, sp()).unwrap();
15675        assert_eq!(ints(&r), vec![3]);
15676    }
15677
15678    #[test]
15679    fn logical_negation_needs_zero_or_one() {
15680        ctx!(c);
15681        let r = not_v().monad(&Array::from_i64(vec![0, 1]), &mut c, sp()).unwrap();
15682        assert_eq!(bools(&r), vec![1, 0]);
15683        let e = not_v().monad(&Array::from_i64(vec![2]), &mut c, sp()).unwrap_err();
15684        assert_eq!(e.kind, ErrorKind::Domain);
15685    }
15686
15687    #[test]
15688    fn signum_abs_and_negation_pick_their_types() {
15689        ctx!(c);
15690        let r = times().monad(&Array::from_i64(vec![-3, 0, 9]), &mut c, sp()).unwrap();
15691        assert_eq!(ints(&r), vec![-1, 0, 1]);
15692        let r = times().monad(&Array::from_f64(vec![-3.0, 0.0, 9.0]), &mut c, sp()).unwrap();
15693        assert_eq!(floats(&r), vec![-1.0, 0.0, 1.0]);
15694        let r = residue_v().monad(&Array::from_i64(vec![-3, 3]), &mut c, sp()).unwrap();
15695        assert_eq!(ints(&r), vec![3, 3]);
15696        let bits = Array::new(vec![2], Data::Bool(vec![0, 1].into()));
15697        let r = minus().monad(&bits, &mut c, sp()).unwrap();
15698        assert_eq!(r.dtype(), DType::I64);
15699        assert_eq!(ints(&r), vec![0, -1]);
15700    }
15701
15702    #[test]
15703    fn square_root_of_a_negative_number_is_complex() {
15704        ctx!(c);
15705        let r = sqrt_v().monad(&Array::from_i64(vec![9]), &mut c, sp()).unwrap();
15706        assert!(close(floats(&r)[0], 3.0));
15707        let r = sqrt_v().monad(&Array::from_i64(vec![-4]), &mut c, sp()).unwrap();
15708        assert_eq!(r.dtype(), DType::Complex);
15709        assert_eq!(r.as_complex_slice().expect("complex data"), &[[0.0, 2.0]]);
15710    }
15711
15712    // --------------------------------------------------------- structural
15713
15714    #[test]
15715    fn shape_tally_and_ravel() {
15716        ctx!(c);
15717        let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15718        let r = dollar().monad(&m, &mut c, sp()).unwrap();
15719        assert_eq!(r.shape, vec![2]);
15720        assert_eq!(ints(&r), vec![2, 3]);
15721        let r = pound().monad(&m, &mut c, sp()).unwrap();
15722        assert!(r.shape.is_empty());
15723        assert_eq!(ints(&r), vec![2]);
15724        // A scalar has one item and no axes.
15725        let r = pound().monad(&Array::scalar_i64(5), &mut c, sp()).unwrap();
15726        assert_eq!(ints(&r), vec![1]);
15727        let r = comma().monad(&m, &mut c, sp()).unwrap();
15728        assert_eq!(r.shape, vec![6]);
15729        assert_eq!(ints(&r), vec![1, 2, 3, 4, 5, 6]);
15730    }
15731
15732    #[test]
15733    fn transpose_reverses_the_axes() {
15734        ctx!(c);
15735        let r = transpose_v().monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
15736        assert_eq!(r.shape, vec![3, 2]);
15737        assert_eq!(ints(&r), vec![1, 4, 2, 5, 3, 6]);
15738        // Rank 3: 2 by 1 by 3 becomes 3 by 1 by 2.
15739        let a = Array::new(vec![2, 1, 3], Data::I64(vec![1, 2, 3, 4, 5, 6].into()));
15740        let r = transpose_v().monad(&a, &mut c, sp()).unwrap();
15741        assert_eq!(r.shape, vec![3, 1, 2]);
15742        assert_eq!(ints(&r), vec![1, 4, 2, 5, 3, 6]);
15743        // Vectors and scalars are unchanged.
15744        let v = Array::from_i64(vec![1, 2]);
15745        assert_eq!(transpose_v().monad(&v, &mut c, sp()).unwrap(), v);
15746    }
15747
15748    #[test]
15749    fn head_and_behead() {
15750        ctx!(c);
15751        let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15752        let r = head_v().monad(&m, &mut c, sp()).unwrap();
15753        assert_eq!(r.shape, vec![3]);
15754        assert_eq!(ints(&r), vec![1, 2, 3]);
15755        let r = behead_v().monad(&m, &mut c, sp()).unwrap();
15756        assert_eq!(r.shape, vec![1, 3]);
15757        assert_eq!(ints(&r), vec![4, 5, 6]);
15758        // The head of an empty array is a cell of fills.
15759        let e = Array::new(vec![0, 2], Data::I64(vec![].into()));
15760        let r = head_v().monad(&e, &mut c, sp()).unwrap();
15761        assert_eq!(r.shape, vec![2]);
15762        assert_eq!(ints(&r), vec![0, 0]);
15763        assert_eq!(behead_v().monad(&e, &mut c, sp()).unwrap(), e);
15764        assert_eq!(head_v().monad(&Array::scalar_i64(5), &mut c, sp()).unwrap().shape, Vec::<usize>::new());
15765        let err = behead_v().monad(&Array::scalar_i64(5), &mut c, sp()).unwrap_err();
15766        assert_eq!(err.kind, ErrorKind::Domain);
15767    }
15768
15769    #[test]
15770    fn iota_fills_a_shape_and_reverses_negative_axes() {
15771        ctx!(c);
15772        let r = iota().monad(&Array::from_i64(vec![2, 3]), &mut c, sp()).unwrap();
15773        assert_eq!(r.shape, vec![2, 3]);
15774        assert_eq!(ints(&r), vec![0, 1, 2, 3, 4, 5]);
15775        // A scalar argument gives one axis.
15776        let r = iota().monad(&Array::scalar_i64(3), &mut c, sp()).unwrap();
15777        assert_eq!(r.shape, vec![3]);
15778        assert_eq!(ints(&r), vec![0, 1, 2]);
15779        // Negative lengths run the axis backwards.
15780        let r = iota().monad(&Array::scalar_i64(-3), &mut c, sp()).unwrap();
15781        assert_eq!(ints(&r), vec![2, 1, 0]);
15782        let r = iota().monad(&Array::from_i64(vec![2, -3]), &mut c, sp()).unwrap();
15783        assert_eq!(r.shape, vec![2, 3]);
15784        assert_eq!(ints(&r), vec![2, 1, 0, 5, 4, 3]);
15785        let r = iota().monad(&Array::from_i64(vec![-2, 3]), &mut c, sp()).unwrap();
15786        assert_eq!(ints(&r), vec![3, 4, 5, 0, 1, 2]);
15787        // Zero lengths give an empty result of that shape.
15788        let r = iota().monad(&Array::scalar_i64(0), &mut c, sp()).unwrap();
15789        assert_eq!(r.shape, vec![0]);
15790        assert!(ints(&r).is_empty());
15791        // Non-integers and matrices are refused.
15792        let e = iota().monad(&Array::from_f64(vec![1.5]), &mut c, sp()).unwrap_err();
15793        assert_eq!(e.kind, ErrorKind::Domain);
15794        let e = iota().monad(&mat(1, 1, vec![1]), &mut c, sp()).unwrap_err();
15795        assert_eq!(e.kind, ErrorKind::Rank);
15796    }
15797
15798    #[test]
15799    fn apl_iota_starts_at_the_index_origin() {
15800        ctx!(c, Agreement::ExactOrScalar);
15801        let r = iota_apl(1).monad(&Array::scalar_i64(3), &mut c, sp()).unwrap();
15802        assert_eq!(ints(&r), vec![1, 2, 3]);
15803        let r = iota_apl(0).monad(&Array::scalar_i64(3), &mut c, sp()).unwrap();
15804        assert_eq!(ints(&r), vec![0, 1, 2]);
15805        let e = iota_apl(1).monad(&Array::scalar_i64(-1), &mut c, sp()).unwrap_err();
15806        assert_eq!(e.kind, ErrorKind::Domain);
15807        // A vector of lengths asks for an array of index vectors, one per
15808        // cell of the result.
15809        let r = iota_apl(1).monad(&Array::from_i64(vec![2, 3]), &mut c, sp()).unwrap();
15810        assert_eq!(r.shape, vec![2, 3]);
15811        assert_eq!(ints(&r.as_boxes().expect("boxed")[4]), vec![2, 2]);
15812    }
15813
15814    #[test]
15815    fn reshape_cycles_the_ravel() {
15816        ctx!(c);
15817        let r = dollar()
15818            .dyad(&Array::from_i64(vec![2, 3]), &Array::from_i64(vec![1, 2]), &mut c, sp())
15819            .unwrap();
15820        assert_eq!(r.shape, vec![2, 3]);
15821        assert_eq!(ints(&r), vec![1, 2, 1, 2, 1, 2]);
15822        // A scalar left argument reshapes to a vector.
15823        let r = dollar()
15824            .dyad(&Array::scalar_i64(3), &Array::from_i64(vec![7]), &mut c, sp())
15825            .unwrap();
15826        assert_eq!(r.shape, vec![3]);
15827        assert_eq!(ints(&r), vec![7, 7, 7]);
15828        // Reshaping down keeps the leading elements, and the type is y's.
15829        let r = dollar()
15830            .dyad(&Array::scalar_i64(2), &Array::from_chars(vec!['a', 'b', 'c']), &mut c, sp())
15831            .unwrap();
15832        assert_eq!(r.dtype(), DType::Char);
15833        // An empty right argument cannot fill a non-empty shape.
15834        let e = dollar()
15835            .dyad(&Array::scalar_i64(2), &Array::empty(DType::I64), &mut c, sp())
15836            .unwrap_err();
15837        assert_eq!(e.kind, ErrorKind::Length);
15838        assert!(e.msg.contains("empty"), "{}", e.msg);
15839        // but an empty shape is fine.
15840        let r = dollar()
15841            .dyad(&Array::scalar_i64(0), &Array::empty(DType::I64), &mut c, sp())
15842            .unwrap();
15843        assert_eq!(r.shape, vec![0]);
15844        let e = dollar()
15845            .dyad(&Array::scalar_i64(-1), &Array::from_i64(vec![1]), &mut c, sp())
15846            .unwrap_err();
15847        assert_eq!(e.kind, ErrorKind::Domain);
15848    }
15849
15850    #[test]
15851    fn take_from_both_ends_and_beyond() {
15852        ctx!(c);
15853        let v = Array::from_i64(vec![1, 2, 3, 4]);
15854        let take = |x: Array, y: &Array, c: &mut Ctx<'_>| head_v().dyad(&x, y, c, sp()).unwrap();
15855        assert_eq!(ints(&take(Array::scalar_i64(2), &v, &mut c)), vec![1, 2]);
15856        assert_eq!(ints(&take(Array::scalar_i64(-2), &v, &mut c)), vec![3, 4]);
15857        // Overtaking pads at the back for a positive count,
15858        let short = Array::from_i64(vec![1, 2, 3]);
15859        assert_eq!(ints(&take(Array::scalar_i64(6), &short, &mut c)), vec![1, 2, 3, 0, 0, 0]);
15860        // and at the front for a negative one.
15861        assert_eq!(ints(&take(Array::scalar_i64(-6), &short, &mut c)), vec![0, 0, 0, 1, 2, 3]);
15862        // A scalar right argument is treated as a one-item vector.
15863        let r = take(Array::scalar_i64(2), &Array::scalar_i64(5), &mut c);
15864        assert_eq!(r.shape, vec![2]);
15865        assert_eq!(ints(&r), vec![5, 0]);
15866        // Per-axis on a matrix.
15867        let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15868        let r = take(Array::scalar_i64(1), &m, &mut c);
15869        assert_eq!(r.shape, vec![1, 3]);
15870        assert_eq!(ints(&r), vec![1, 2, 3]);
15871        let r = take(Array::scalar_i64(-1), &m, &mut c);
15872        assert_eq!(ints(&r), vec![4, 5, 6]);
15873        let r = take(Array::from_i64(vec![2, 2]), &m, &mut c);
15874        assert_eq!(r.shape, vec![2, 2]);
15875        assert_eq!(ints(&r), vec![1, 2, 4, 5]);
15876        let r = take(Array::from_i64(vec![3, -2]), &m, &mut c);
15877        assert_eq!(r.shape, vec![3, 2]);
15878        assert_eq!(ints(&r), vec![2, 3, 5, 6, 0, 0]);
15879        // Character fills are spaces.
15880        let r = head_v()
15881            .dyad(&Array::scalar_i64(3), &Array::from_chars(vec!['a']), &mut c, sp())
15882            .unwrap();
15883        assert_eq!(r.data, Data::Char(vec!['a', ' ', ' '].into()));
15884        // More counts than the argument has axes: a length error, as both
15885        // references answer. Only a scalar right argument stretches.
15886        let e = head_v()
15887            .dyad(&Array::from_i64(vec![1, 1]), &Array::from_i64(vec![1, 2]), &mut c, sp())
15888            .unwrap_err();
15889        assert_eq!(e.kind, ErrorKind::Length);
15890        let r = head_v()
15891            .dyad(&Array::from_i64(vec![1, 2]), &Array::scalar_i64(5), &mut c, sp())
15892            .unwrap();
15893        assert_eq!(r.shape, vec![1, 2]);
15894        assert_eq!(ints(&r), vec![5, 0]);
15895    }
15896
15897    #[test]
15898    fn drop_from_both_ends_and_beyond() {
15899        ctx!(c);
15900        let v = Array::from_i64(vec![1, 2, 3]);
15901        let drop = |x: Array, y: &Array, c: &mut Ctx<'_>| behead_v().dyad(&x, y, c, sp()).unwrap();
15902        assert_eq!(ints(&drop(Array::scalar_i64(1), &v, &mut c)), vec![2, 3]);
15903        assert_eq!(ints(&drop(Array::scalar_i64(-1), &v, &mut c)), vec![1, 2]);
15904        // Dropping more than there is empties the axis.
15905        let r = drop(Array::scalar_i64(5), &v, &mut c);
15906        assert_eq!(r.shape, vec![0]);
15907        assert!(ints(&r).is_empty());
15908        let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
15909        let r = drop(Array::scalar_i64(1), &m, &mut c);
15910        assert_eq!(r.shape, vec![1, 3]);
15911        assert_eq!(ints(&r), vec![4, 5, 6]);
15912        let r = drop(Array::from_i64(vec![0, -1]), &m, &mut c);
15913        assert_eq!(r.shape, vec![2, 2]);
15914        assert_eq!(ints(&r), vec![1, 2, 4, 5]);
15915    }
15916
15917    // ------------------------------------------------------------ framing
15918
15919    #[test]
15920    fn cells_of_unequal_shapes_are_padded_with_fills() {
15921        ctx!(c);
15922        // i."0 ] 1 2 3: cells of length 1, 2 and 3 frame into a 3 by 3 table.
15923        let v = Verb::Rank(b(iota()), [0, 0, 0]);
15924        let r = v.monad(&Array::from_i64(vec![1, 2, 3]), &mut c, sp()).unwrap();
15925        assert_eq!(r.shape, vec![3, 3]);
15926        assert_eq!(ints(&r), vec![0, 0, 0, 0, 1, 0, 0, 1, 2]);
15927    }
15928
15929    #[test]
15930    fn framing_aligns_lower_rank_cells_at_the_trailing_axes() {
15931        let cells = vec![Array::from_i64(vec![1, 2]), mat(2, 2, vec![1, 2, 3, 4])];
15932        let r = assemble(&[2], cells, sp()).unwrap();
15933        assert_eq!(r.shape, vec![2, 2, 2]);
15934        assert_eq!(ints(&r), vec![1, 2, 0, 0, 1, 2, 3, 4]);
15935    }
15936
15937    #[test]
15938    fn framing_promotes_cell_types() {
15939        let cells = vec![Array::from_i64(vec![1]), Array::from_f64(vec![2.5])];
15940        let r = assemble(&[2], cells, sp()).unwrap();
15941        assert_eq!(r.dtype(), DType::F64);
15942        assert_eq!(floats(&r), vec![1.0, 2.5]);
15943        // Characters and numbers cannot share a result.
15944        let cells = vec![Array::from_i64(vec![1]), Array::from_chars(vec!['a'])];
15945        let e = assemble(&[2], cells, sp()).unwrap_err();
15946        assert_eq!(e.kind, ErrorKind::Type);
15947    }
15948
15949    #[test]
15950    fn framing_over_an_empty_frame_yields_an_empty_result() {
15951        let r = assemble(&[0], Vec::new(), sp()).unwrap();
15952        assert_eq!(r.shape, vec![0]);
15953        assert_eq!(r.count(), 0);
15954    }
15955
15956    // ------------------------------------------------------------- trains
15957
15958    #[test]
15959    fn fork_applies_both_tines() {
15960        ctx!(c);
15961        // (+/ % #) is the mean.
15962        let v = Verb::Fork(b(Verb::Reduce(b(plus()))), b(pct()), b(pound()));
15963        let r = v.monad(&Array::from_i64(vec![1, 2, 3, 4]), &mut c, sp()).unwrap();
15964        assert!(close(floats(&r)[0], 2.5));
15965        // Dyadically both tines see both arguments: (x-y) + (x+y) = 2x.
15966        let v = Verb::Fork(b(minus()), b(plus()), b(plus()));
15967        let r = v
15968            .dyad(&Array::from_i64(vec![5]), &Array::from_i64(vec![3]), &mut c, sp())
15969            .unwrap();
15970        assert_eq!(ints(&r), vec![10]);
15971    }
15972
15973    #[test]
15974    fn noun_fork_supplies_a_constant_left_argument() {
15975        ctx!(c);
15976        let v = Verb::NounFork(Array::scalar_i64(10), b(minus()), b(right_v()));
15977        let r = v.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
15978        assert_eq!(ints(&r), vec![9, 8]);
15979        let r = v
15980            .dyad(&Array::scalar_i64(0), &Array::from_i64(vec![1, 2]), &mut c, sp())
15981            .unwrap();
15982        assert_eq!(ints(&r), vec![9, 8]);
15983    }
15984
15985    #[test]
15986    fn hook_reuses_its_right_argument() {
15987        ctx!(c);
15988        // y + (-y) is zero.
15989        let v = Verb::Hook(b(plus()), b(minus()));
15990        let r = v.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
15991        assert_eq!(ints(&r), vec![0, 0]);
15992        // x + (-y)
15993        let r = v
15994            .dyad(&Array::from_i64(vec![10]), &Array::from_i64(vec![3]), &mut c, sp())
15995            .unwrap();
15996        assert_eq!(ints(&r), vec![7]);
15997    }
15998
15999    #[test]
16000    fn atop_composes() {
16001        ctx!(c);
16002        let v = Verb::Atop(b(minus()), b(plus()));
16003        let r = v.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
16004        assert_eq!(ints(&r), vec![-1, -2]);
16005        let r = v
16006            .dyad(&Array::from_i64(vec![1]), &Array::from_i64(vec![2]), &mut c, sp())
16007            .unwrap();
16008        assert_eq!(ints(&r), vec![-3]);
16009    }
16010
16011    #[test]
16012    fn trains_apply_to_the_whole_argument() {
16013        // No train iterates cells of its own.
16014        assert_eq!(Verb::Hook(b(plus()), b(minus())).ranks(), [RANK_INF; 3]);
16015        assert_eq!(Verb::Reduce(b(plus())).ranks(), [RANK_INF; 3]);
16016    }
16017
16018    // ------------------------------------------------------- missing cases
16019
16020    #[test]
16021    fn absent_and_unwritten_meanings_are_reported_differently() {
16022        ctx!(c);
16023        let e = eq_v().monad(&Array::scalar_i64(1), &mut c, sp()).unwrap_err();
16024        assert_eq!(e.kind, ErrorKind::Domain);
16025        assert!(e.msg.contains("no monadic meaning"), "{}", e.msg);
16026        let e = not_v()
16027            .dyad(&Array::scalar_i64(1), &Array::scalar_i64(1), &mut c, sp())
16028            .unwrap_err();
16029        assert_eq!(e.kind, ErrorKind::Domain);
16030        assert!(e.msg.contains("no dyadic meaning"), "{}", e.msg);
16031        let e = pound()
16032            .dyad(&Array::scalar_i64(1), &Array::scalar_i64(1), &mut c, sp())
16033            .unwrap_err();
16034        assert_eq!(e.kind, ErrorKind::NotYet);
16035        assert!(e.msg.contains("copy"), "{}", e.msg);
16036        // Echo's output formatting belongs to fmt; only its result is checked.
16037        let _ = echo_v();
16038    }
16039
16040    // ----------------------------------------------------- parallel paths
16041    //
16042    // Every case here runs the same application twice, on a pool of one
16043    // thread and on a pool of four, and compares the two: the sequential
16044    // result is the contract, and the argument sizes are chosen to be over
16045    // the threshold so the parallel path is really taken.
16046
16047    /// The result of `f` under one thread and under four.
16048    fn seq_par<T: Send>(f: impl Fn() -> T + Sync + Send) -> (T, T) {
16049        (par::with_threads(1, &f), par::with_threads(4, &f))
16050    }
16051
16052    /// A deterministic spread of values, positive and negative.
16053    fn noise(n: usize) -> Vec<f64> {
16054        let mut x = 0x2545_f491_4f6c_dd1du64;
16055        (0..n)
16056            .map(|_| {
16057                x ^= x << 13;
16058                x ^= x >> 7;
16059                x ^= x << 17;
16060                (x >> 11) as f64 / (1u64 << 53) as f64 - 0.5
16061            })
16062            .collect()
16063    }
16064
16065    fn f64_mat(rows: usize, cols: usize) -> Array {
16066        Array::new(vec![rows, cols], Data::F64(noise(rows * cols).into()))
16067    }
16068
16069    /// Above `par::MIN_WORK`, so anything elementwise splits.
16070    const BIG: usize = 200_000;
16071
16072    #[test]
16073    fn an_elementwise_dyad_splits_into_the_same_result() {
16074        let x = Array::from_f64(noise(BIG));
16075        let y = Array::from_f64(noise(BIG).iter().map(|v| v + 0.25).collect());
16076        let (one, many) = seq_par(|| {
16077            ctx!(c);
16078            times().dyad(&x, &y, &mut c, sp()).unwrap()
16079        });
16080        assert_eq!(floats(&one), floats(&many));
16081        // A scalar left argument takes the broadcasting shape of the loop.
16082        let (one, many) = seq_par(|| {
16083            ctx!(c);
16084            plus().dyad(&Array::scalar_f64(0.5), &y, &mut c, sp()).unwrap()
16085        });
16086        assert_eq!(floats(&one), floats(&many));
16087    }
16088
16089    #[test]
16090    fn an_elementwise_dyad_that_overflows_widens_the_same_way() {
16091        // One pair overflows i64, so the whole pass is redone in floats
16092        // however the chunks fell.
16093        let mut v = vec![1i64; BIG];
16094        v[BIG - 3] = i64::MAX;
16095        let x = Array::from_i64(v);
16096        let (one, many) = seq_par(|| {
16097            ctx!(c);
16098            plus().dyad(&x, &x, &mut c, sp()).unwrap()
16099        });
16100        assert_eq!(one.dtype(), DType::F64);
16101        assert_eq!(floats(&one), floats(&many));
16102    }
16103
16104    #[test]
16105    fn an_elementwise_monad_splits_into_the_same_result() {
16106        let y = Array::from_f64(noise(BIG));
16107        for v in [minus(), sqrt_v(), floor_v(), pct()] {
16108            let (one, many) = seq_par(|| {
16109                ctx!(c);
16110                v.monad(&Array::from_f64(y.as_f64_slice().unwrap().iter().map(|x| x.abs()).collect()), &mut c, sp())
16111                    .unwrap()
16112            });
16113            assert_eq!(one.data, many.data, "{}", v.name());
16114        }
16115    }
16116
16117    #[test]
16118    fn monadic_cells_run_in_parallel_and_frame_in_order() {
16119        // 400 cells of 512 elements: over the threshold, and every cell
16120        // yields a different value, so a misplaced cell would show.
16121        let y = f64_mat(400, 512);
16122        let v = Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]);
16123        let (one, many) = seq_par(|| {
16124            ctx!(c);
16125            v.monad(&y, &mut c, sp()).unwrap()
16126        });
16127        assert_eq!(one.shape, vec![400]);
16128        assert_eq!(floats(&one), floats(&many));
16129    }
16130
16131    #[test]
16132    fn dyadic_cells_run_in_parallel_and_frame_in_order() {
16133        let x = f64_mat(400, 512);
16134        let y = f64_mat(400, 512);
16135        // Rank 1: the frame is the rows, and each row pair is one cell.
16136        let v = Verb::Rank(b(plus()), [1, 1, 1]);
16137        let (one, many) = seq_par(|| {
16138            ctx!(c);
16139            v.dyad(&x, &y, &mut c, sp()).unwrap()
16140        });
16141        assert_eq!(one.shape, vec![400, 512]);
16142        assert_eq!(floats(&one), floats(&many));
16143    }
16144
16145    #[test]
16146    fn a_verb_that_writes_output_is_not_pure() {
16147        assert!(plus().is_pure());
16148        assert!(Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]).is_pure());
16149        assert!(!echo_v().is_pure());
16150        assert!(!Verb::Rank(b(Verb::Atop(b(echo_v()), b(plus()))), [1, 1, 1]).is_pure());
16151    }
16152
16153    #[test]
16154    fn an_impure_verb_keeps_its_cells_in_order() {
16155        // Enough elements to pass the threshold; the cells must still be
16156        // written one after another, in index order.
16157        let y = Array::new(vec![16, 8192], Data::I64((0..16 * 8192).collect::<Vec<i64>>().into()));
16158        let v = Verb::Rank(b(Verb::Atop(b(echo_v()), b(head_v()))), [1, 1, 1]);
16159        let mut seen: Vec<i64> = Vec::new();
16160        let mut sink = |s: &str| {
16161            if let Some(first) = s.split_whitespace().next() && let Ok(n) = first.parse::<i64>() {
16162                seen.push(n);
16163            }
16164        };
16165        let mut env = Env::new(Vec::new());
16166        let mut c = Ctx {
16167            cfg: EvalCfg {
16168                agreement: Agreement::LeadingPrefix,
16169                fmt: FmtOpts::J,
16170                tol: Tol::J,
16171                rules: Rules::default(),
16172            },
16173            out: &mut sink,
16174            inp: None,
16175            env: &mut env,
16176            device: None,
16177            shy: false,
16178        };
16179        v.monad(&y, &mut c, sp()).unwrap();
16180        assert_eq!(seen, (0..16).map(|i| i * 8192).collect::<Vec<i64>>());
16181    }
16182
16183    #[test]
16184    fn a_wide_item_reduce_folds_every_column_in_order() {
16185        // item_size over par::WIDE_ITEM: each output element folds its own
16186        // column, so even a non-associative fold matches exactly.
16187        let y = f64_mat(300, 512);
16188        for v in [plus(), minus(), floor_v()] {
16189            let (one, many) = seq_par(|| {
16190                ctx!(c);
16191                Verb::Reduce(b(v.clone())).monad(&y, &mut c, sp()).unwrap()
16192            });
16193            assert_eq!(one.shape, vec![512]);
16194            assert_eq!(floats(&one), floats(&many), "{}", v.name());
16195        }
16196    }
16197
16198    #[test]
16199    fn a_wide_item_integer_reduce_is_exact() {
16200        let n = 300;
16201        let m = 512;
16202        let y = Array::new(
16203            vec![n, m],
16204            Data::I64((0..(n * m) as i64).map(|i| i % 977 - 400).collect::<Vec<i64>>().into()),
16205        );
16206        let (one, many) = seq_par(|| {
16207            ctx!(c);
16208            Verb::Reduce(b(minus())).monad(&y, &mut c, sp()).unwrap()
16209        });
16210        assert_eq!(ints(&one), ints(&many));
16211    }
16212
16213    #[test]
16214    fn a_narrow_item_reduce_chunks_the_items() {
16215        // item_size under par::WIDE_ITEM and an associative verb: the items
16216        // are chunked, which reassociates a float sum (§5.9) but not an
16217        // integer one.
16218        let y = f64_mat(300_000, 8);
16219        let (one, many) = seq_par(|| {
16220            ctx!(c);
16221            Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
16222        });
16223        assert_eq!(one.shape, vec![8]);
16224        for (p, q) in floats(&one).iter().zip(floats(&many)) {
16225            assert!((p - q).abs() <= 1e-12 * p.abs().max(1.0), "{p} vs {q}");
16226        }
16227        let ints_y = Array::new(
16228            vec![300_000, 8],
16229            Data::I64((0..300_000 * 8).map(|i| (i % 101) as i64 - 50).collect::<Vec<i64>>().into()),
16230        );
16231        let (one, many) = seq_par(|| {
16232            ctx!(c);
16233            Verb::Reduce(b(plus())).monad(&ints_y, &mut c, sp()).unwrap()
16234        });
16235        assert_eq!(ints(&one), ints(&many));
16236    }
16237
16238    #[test]
16239    fn a_vector_reduce_folds_the_flat_buffer() {
16240        let y = Array::from_f64(noise(BIG * 4));
16241        let (one, many) = seq_par(|| {
16242            ctx!(c);
16243            Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
16244        });
16245        let (p, q) = (floats(&one)[0], floats(&many)[0]);
16246        assert!((p - q).abs() <= 1e-12 * p.abs().max(1.0), "{p} vs {q}");
16247
16248        // Integers are exact, and a non-associative fold is not regrouped
16249        // at all, so it matches to the bit.
16250        let ints_y = Array::from_i64((0..BIG as i64 * 4).map(|i| i % 1009 - 500).collect());
16251        for v in [plus(), minus(), ceil_v()] {
16252            let (one, many) = seq_par(|| {
16253                ctx!(c);
16254                Verb::Reduce(b(v.clone())).monad(&ints_y, &mut c, sp()).unwrap()
16255            });
16256            assert_eq!(ints(&one), ints(&many), "{}", v.name());
16257        }
16258    }
16259
16260    #[test]
16261    fn a_reduce_that_overflows_falls_back_to_the_sequential_widening() {
16262        let mut v: Vec<i64> = vec![1; BIG];
16263        v[7] = i64::MAX;
16264        let y = Array::from_i64(v);
16265        let (one, many) = seq_par(|| {
16266            ctx!(c);
16267            Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
16268        });
16269        assert_eq!(one.dtype(), DType::F64);
16270        assert_eq!(floats(&one), floats(&many));
16271    }
16272
16273    #[test]
16274    fn a_boolean_reduce_matches_the_sequential_promotion() {
16275        let n = BIG;
16276        let y = Array::new(
16277            vec![n],
16278            Data::Bool((0..n).map(|i| (i % 3 == 0) as u8).collect::<Vec<u8>>().into()),
16279        );
16280        let (one, many) = seq_par(|| {
16281            ctx!(c);
16282            Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
16283        });
16284        assert_eq!(one.dtype(), DType::I64);
16285        assert_eq!(ints(&one), ints(&many));
16286        assert_eq!(ints(&one)[0], n.div_ceil(3) as i64);
16287    }
16288}