Skip to main content

jay/frontend/
apl.rs

1//! APL frontend: lexer and parser, lowering to the shared IR.
2//!
3//! APL sentences read right to left: the rightmost expression is the right
4//! argument of the function to its left, and a function is dyadic exactly
5//! when an operand ends immediately to its left. Operators bind tighter than
6//! that: `f/` and `f⍤r` are folded into derived functions before the
7//! sentence is parsed.
8
9use std::collections::HashMap;
10use std::sync::Arc;
11
12use crate::array::{Array, Data};
13use crate::error::{Error, Result, Span};
14use crate::frontend::{
15    DefaultArg, DepthSign, DfnResult, FirstDisclose, IndexForm, LookupLeft, NestedModel, Partition,
16    Rules, Segment, SourceParts,
17};
18use crate::ir::{Branch, Control, ExplicitDef, Expr, Scope};
19use crate::verb::{
20    BoolDyad, DyadOp, Enclose, MonadOp, OpDef, Operand, Power, Prim, ScalarDyad, ScalarMonad,
21    Verb, WindowKind,
22    RANK_INF,
23};
24
25/// Parse an APL program (sentences separated by newlines or `⋄`) into IR
26/// statements. `d` is the dialect, resolved: `⎕IO`, `⎕CT` and the
27/// lineage settings the parser reads.
28pub fn parse(src: &SourceParts, d: Rules) -> Result<Vec<Expr>> {
29    let sentences = lex(src, d)?;
30    let mut verbs: HashMap<String, Verb> = HashMap::new();
31    let mut stmts = Vec::with_capacity(sentences.len());
32    let mut i = 0usize;
33    while i < sentences.len() {
34        if matches!(sentences[i].first().map(|t| &t.kind), Some(Tok::Del)) {
35            let stmt = parse_tradfn(&sentences, &mut i, d, &mut verbs)?;
36            stmts.push(stmt);
37            continue;
38        }
39        let mut sentence = sentences[i].clone();
40        i += 1;
41        // `⎕FX` defines a function where it stands; the sentence keeps only
42        // the name it answers with.
43        while let Some(at) = outermost_fx(&sentence) {
44            let mut end = at + 1;
45            while end < sentence.len() && matches!(sentence[end].kind, Tok::Value(_)) {
46                end += 1;
47            }
48            let (def, name) = fix_definition(&sentence[at..end], d, &mut verbs)?;
49            stmts.push(def);
50            let span = Span::merge(sentence[at].span, sentence[end - 1].span);
51            sentence.splice(at..end, [Token { kind: Tok::Value(name), span }]);
52        }
53        if let Some(stmt) = parse_statement(sentence, d, &mut verbs, false)? {
54            stmts.push(stmt);
55        }
56    }
57    Ok(stmts)
58}
59
60/// Where a `⎕FX` stands in a sentence of its own rather than inside a dfn's
61/// braces. One inside a body belongs to that body, which is compiled when
62/// the definition is, so it is left where it is and named there as a gap.
63fn outermost_fx(sentence: &[Token]) -> Option<usize> {
64    let mut depth = 0usize;
65    for (i, t) in sentence.iter().enumerate() {
66        match t.kind {
67            Tok::LBrace => depth += 1,
68            Tok::RBrace => depth = depth.saturating_sub(1),
69            Tok::QuadFx if depth == 0 => return Some(i),
70            _ => {}
71        }
72    }
73    None
74}
75
76/// `⎕FX`: fix a definition from its text. `toks` opens with the `⎕FX`
77/// itself and runs to the end of the sentence; the lines are a vector of
78/// character vectors, one per line of the definition, the first of them the
79/// header. The answer is the name the definition gives the function.
80///
81/// libjay compiles before it runs, so the lines have to be text the
82/// compiler can read: a definition assembled while the program runs is a
83/// promise, not a refusal. Dyalog's own answer for a definition it cannot
84/// fix is the number of the offending line; libjay reports the fault
85/// instead, pointing at the line that carries it.
86fn fix_definition(
87    toks: &[Token],
88    d: Rules,
89    verbs: &mut HashMap<String, Verb>,
90) -> Result<(Expr, Array)> {
91    let fx = toks[0].span;
92    if toks.len() == 1 {
93        return Err(Error::not_yet(
94            "⎕FX on a definition that is not literal text in the program",
95            fx,
96        ));
97    }
98    let span = Span::merge(fx, toks[toks.len() - 1].span);
99    let mut lines: Vec<(Vec<Token>, Span)> = Vec::new();
100    for t in &toks[1..] {
101        let text = match &t.kind {
102            Tok::Value(a) if a.rank() <= 1 => match &a.data {
103                Data::Char(cs) => Some(cs.iter().collect::<String>()),
104                _ => None,
105            },
106            _ => None,
107        };
108        let Some(text) = text else {
109            return Err(Error::not_yet(
110                "⎕FX on a definition that is not literal text in the program",
111                t.span,
112            ));
113        };
114        let src = SourceParts::from_parts(&[&text], &[]);
115        let mut lexed = lex(&src, d)?;
116        if lexed.len() > 1 {
117            return Err(Error::not_yet("a ⋄ inside a ⎕FX line", t.span));
118        }
119        // Every span inside the line indexes the line's own text, so it is
120        // brought back to the literal the line came from.
121        let mut line = lexed.pop().unwrap_or_default();
122        for tok in &mut line {
123            tok.span = t.span;
124        }
125        lines.push((line, t.span));
126    }
127    let (head, head_span) = lines.remove(0);
128    if head.is_empty() {
129        return Err(Error::parse("⎕FX starts with the definition's header", head_span));
130    }
131    let body: Vec<Vec<Token>> = lines.into_iter().map(|(l, _)| l).collect();
132    let def = build_tradfn(&head, &body, span, d, verbs)?;
133    let Expr::VerbDef { name, .. } = &def else {
134        return Err(Error::internal("⎕FX did not build a definition"));
135    };
136    let answer = Array::from_chars(name.chars().collect());
137    Ok((def, answer))
138}
139
140/// One sentence, with every name known to be a function already a function.
141/// None where the sentence held nothing but blanks and a comment.
142fn parse_statement(
143    sentence: Vec<Token>,
144    d: Rules,
145    verbs: &mut HashMap<String, Verb>,
146    // True where `verbs` is the ENCLOSING program's map rather than this
147    // definition's own, so that a function this sentence names must not be
148    // registered in it. A dfn body parses against a clone of its own and
149    // registers there, which is what lets `G←{⍵×2} ⋄ G ⍵` read `G` as the
150    // function it named.
151    shared_verbs: bool,
152) -> Result<Option<Expr>> {
153    // A `⎕FX` that reaches here is inside another definition's body: the
154    // one that stands on its own was fixed and rewritten away before the
155    // sentence was parsed.
156    if let Some(t) = sentence.iter().find(|t| matches!(t.kind, Tok::QuadFx)) {
157        return Err(Error::not_yet("⎕FX inside another definition", t.span));
158    }
159    let sentence = substitute_verbs(sentence, verbs);
160    let sentence = fold_dfns(sentence, d, verbs)?;
161    // `F←{⍵×2}` names a function: the sentence does no work at run time, and
162    // later sentences read `F` as the function itself.
163    if let [name, assign, func] = &sentence[..]
164        && let (Tok::Name(n), Tok::Assign) = (&name.kind, &assign.kind)
165    {
166        // A dfn that mentions `⍺⍺` or `⍵⍵` is an operator; naming it
167        // keeps it one, waiting for the operands.
168        let named = match &func.kind {
169            Tok::Func(v) => Some(v.clone()),
170            Tok::UserOp { def, omega } => Some(unapplied_op(def.clone(), *omega)),
171            _ => None,
172        };
173        if let Some(v) = named {
174            let span = Span::merge(name.span, func.span);
175            if !shared_verbs {
176                verbs.insert(n.clone(), v.clone());
177            }
178            return Ok(Some(Expr::VerbDef { name: n.clone(), verb: v, span }));
179        }
180    }
181    let toks = fold_axes(fold_operators(unwrap_lone_operators(sentence), d)?, d)?;
182    if toks.is_empty() {
183        return Ok(None);
184    }
185    // `F←+/` and `F←+/÷≢` name a function, derived or tacit. Like the dfn
186    // above, the sentence does no work at run time and later sentences read
187    // the name as the function itself.
188    if let [name, assign, rest @ ..] = &toks[..]
189        && let (Tok::Name(n), Tok::Assign) = (&name.kind, &assign.kind)
190        && let Some(v) = tine_run(rest, d)?
191    {
192        let span = Span::merge(name.span, toks[toks.len() - 1].span);
193        if !shared_verbs {
194            verbs.insert(n.clone(), v.clone());
195        }
196        return Ok(Some(Expr::VerbDef { name: n.clone(), verb: v, span }));
197    }
198    if let Some(t) = toks.iter().find(|t| matches!(t.kind, Tok::Control(_))) {
199        return Err(Error::parse(
200            "control structures are only meaningful inside a ∇ definition",
201            t.span,
202        ));
203    }
204    if let Some(t) = toks.iter().find(|t| matches!(t.kind, Tok::Arrow)) {
205        return Err(Error::parse(
206            "→ branches, and only a line of a ∇ definition may begin with it",
207            t.span,
208        ));
209    }
210    let hint = Span::merge(toks[0].span, toks[toks.len() - 1].span);
211    // `A[i]←v` replaces part of a named value; nothing else assigns through
212    // a bracket.
213    if let Some(e) = indexed_assignment(&toks, d, hint)? {
214        return Ok(Some(e));
215    }
216    parse_range(&toks, 0, toks.len(), hint, d).map(Some)
217}
218
219/// Replace every name the program has given a function by that function,
220/// except where the name is the target of an assignment.
221fn substitute_verbs(mut toks: Vec<Token>, verbs: &HashMap<String, Verb>) -> Vec<Token> {
222    for i in 0..toks.len() {
223        let Tok::Name(n) = &toks[i].kind else { continue };
224        if matches!(toks.get(i + 1).map(|t| &t.kind), Some(Tok::Assign)) {
225            continue;
226        }
227        if let Some(v) = verbs.get(n) {
228            // A niladic definition is called by naming it, so its name
229            // stands where a value does, not where a function does.
230            toks[i].kind = match as_user_op(v) {
231                Some((def, omega)) => Tok::UserOp { def, omega },
232                None if is_niladic(v) => Tok::Niladic(v.clone()),
233                None => Tok::Func(v.clone()),
234            };
235        }
236    }
237    toks
238}
239
240// ---------------------------------------------------------------------------
241// Tokens
242// ---------------------------------------------------------------------------
243
244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
245enum OpGlyph {
246    /// `/` — reduce along the last axis.
247    Slash,
248    /// `⌿` — reduce along the leading axis.
249    SlashBar,
250    /// `\` — scan along the last axis.
251    Backslash,
252    /// `⍀` — scan along the leading axis.
253    BackslashBar,
254    /// `⍤` — rank.
255    Rank,
256    /// `⍨` — commute.
257    Commute,
258    /// `⍣` — power.
259    Power,
260    /// `∘.` — outer product; unlike the rest, its operand is on its right.
261    JotDot,
262    /// `⍥` over: `f⍥g` prepares both arguments with g, then applies f.
263    Over,
264    /// `⍢` under (Dyalog): `f⍢g` is `g⁻¹ (g x) f (g y)` — over, undone.
265    Under,
266    /// `⌺` stencil (Dyalog): f over the window centred on each cell.
267    Stencil,
268    /// `∘` on its own — Dyalog's `f∘g`, which libjay does not have yet.
269    Jot,
270    /// `¨` — each.
271    Each,
272    /// `⍛` before: `f⍛g` prepares the LEFT argument with f.
273    Before,
274    /// `⌸` key (Dyalog): each distinct major cell with what shares it.
275    Key,
276    /// `.` between two functions: the inner product, `+.×` above all. The
277    /// operand on its right is a function too, as `∘.`'s is.
278    Dot,
279    /// `⍠` variant: one dialect knob overridden for this application.
280    Variant,
281}
282
283impl OpGlyph {
284    fn glyph(self) -> char {
285        match self {
286            OpGlyph::Slash => '/',
287            OpGlyph::SlashBar => '⌿',
288            OpGlyph::Backslash => '\\',
289            OpGlyph::BackslashBar => '⍀',
290            OpGlyph::Rank => '⍤',
291            OpGlyph::Commute => '⍨',
292            OpGlyph::Power => '⍣',
293            OpGlyph::JotDot | OpGlyph::Jot => '∘',
294            OpGlyph::Over => '⍥',
295            OpGlyph::Under => '⍢',
296            OpGlyph::Stencil => '⌺',
297            OpGlyph::Each => '¨',
298            OpGlyph::Before => '⍛',
299            OpGlyph::Key => '⌸',
300            OpGlyph::Dot => '.',
301            OpGlyph::Variant => '⍠',
302        }
303    }
304}
305
306#[derive(Clone, Debug)]
307enum Tok {
308    /// A literal array: a character array, or a value built by the lexer.
309    Value(Array),
310    /// A run of adjacent numeric literals. Apart from `Value` because
311    /// vector notation spreads its numbers into separate items while a
312    /// string contributes one.
313    Nums(Array),
314    /// An interpolation hole, by parameter index.
315    Param(usize),
316    Name(String),
317    /// A primitive or derived function.
318    Func(Verb),
319    /// An operator glyph; gone after `fold_operators`.
320    Op(OpGlyph),
321    Assign,
322    /// `⎕` or `⍞` standing alone: input where a value belongs, output
323    /// where `←` follows it. `quote` is the `⍞` form.
324    Quad { quote: bool },
325    LParen,
326    RParen,
327    LBracket,
328    RBracket,
329    /// The separator between index slots inside `[ ]`, and the one between
330    /// a `∇`-definition header's name and its locals.
331    Semi,
332    /// `{` and `}`, a dfn's brackets; gone after `fold_dfns`.
333    LBrace,
334    RBrace,
335    /// A statement break inside a dfn's braces, where `⋄` and a line break
336    /// do not end the sentence the dfn belongs to.
337    Separator,
338    /// The `:` of a dfn's guard, `cond:expr`.
339    Colon,
340    /// `→` — the branch, which only a `∇` definition's line may open with.
341    Arrow,
342    /// A niladic `∇` definition named where a value belongs: naming it is
343    /// what calls it.
344    Niladic(Verb),
345    /// A dfn that mentions `⍺⍺` or `⍵⍵`: an operator, waiting for its
346    /// operands. `omega` says whether it wants one on its right too.
347    UserOp { def: Arc<OpDef>, omega: bool },
348    /// `∇`: a definition's bracket outside a dfn, a self-reference inside.
349    Del,
350    /// A control word, `:If` and its family, without the colon.
351    Control(&'static str),
352    /// `⎕FX`, which fixes a definition from its text. It is not a function
353    /// the sentence parser can carry: the definition is built while the
354    /// program is compiled, so the whole sentence is rewritten around it.
355    QuadFx,
356}
357
358#[derive(Clone, Debug)]
359struct Token {
360    kind: Tok,
361    span: Span,
362}
363
364/// True for tokens that can end an operand (and so make the function on
365/// their right dyadic).
366fn is_operand_end(k: &Tok) -> bool {
367    matches!(
368        k,
369        Tok::Value(_)
370            | Tok::Nums(_)
371            | Tok::Param(_)
372            | Tok::Name(_)
373            | Tok::Niladic(_)
374            // `⍞` and `⎕` are values where nothing assigns to them, so an
375            // operand ends at one: `⍞,⍞` is a dyad and `⍞[1]` indexes the
376            // line. The `⍞←` form never reaches here — the parser reads
377            // the assignment arrow before it looks left.
378            | Tok::Quad { .. }
379            | Tok::RParen
380            | Tok::RBracket
381    )
382}
383
384/// A user-written operator with no operands yet: the two names stand in
385/// for them until it is applied, which is how a NAMED operator survives
386/// from the sentence that defined it to the one that uses it.
387fn unapplied_op(def: Arc<OpDef>, omega: bool) -> Verb {
388    let named = |n: &str| Operand::Func(Box::new(Verb::Named(n.to_string())));
389    Verb::UserDerived {
390        def,
391        alpha: named("⍺⍺"),
392        omega: omega.then(|| named("⍵⍵")),
393    }
394}
395
396/// The definition and right-operand appetite of an operator that is still
397/// waiting for its operands.
398fn as_user_op(v: &Verb) -> Option<(Arc<OpDef>, bool)> {
399    let Verb::UserDerived { def, alpha, omega } = v else { return None };
400    // A left operand still standing under its own name is what marks an
401    // operator that has not been applied to anything yet.
402    let Operand::Func(f) = alpha else { return None };
403    match &**f {
404        Verb::Named(n) if n == "⍺⍺" => Some((def.clone(), omega.is_some())),
405        _ => None,
406    }
407}
408
409/// True for a `∇` definition that takes no argument.
410fn is_niladic(v: &Verb) -> bool {
411    matches!(v, Verb::Explicit(d) if d.left.is_none() && d.right == crate::ir::NILADIC)
412}
413
414/// The array a literal token holds.
415fn literal(k: &Tok) -> Option<&Array> {
416    match k {
417        Tok::Value(a) | Tok::Nums(a) => Some(a),
418        _ => None,
419    }
420}
421
422// ---------------------------------------------------------------------------
423// Primitive table
424// ---------------------------------------------------------------------------
425
426/// The primitive for a function glyph, under the dialect `d`: `⎕IO`
427/// parameterises the counting primitives, and the lineage settings decide
428/// the glyphs the APL lines read differently (`↑ ⊃ ⊂ ⌷`).
429///
430/// A glyph whose meaning this dialect does not have is `None` here, as an
431/// unknown glyph is — but a dialect that reads one differently is refused
432/// by [`Dialect::rules`](crate::Dialect::rules) before a program reaches
433/// this table, so `None` here is only ever the unknown glyph.
434fn prim_for(ch: char, d: Rules) -> Option<Prim> {
435    use DyadOp as D;
436    use MonadOp as M;
437    use ScalarDyad as SD;
438    use ScalarMonad as SM;
439    let origin = d.origin;
440    let p = match ch {
441        '+' => Prim {
442            name: "+",
443            monad: M::Scalar(SM::Conj),
444            dyad: D::Scalar(SD::Add),
445            ranks: [0, 0, 0],
446        },
447        '-' => {
448            Prim { name: "-", monad: M::Scalar(SM::Neg), dyad: D::Scalar(SD::Sub), ranks: [0, 0, 0] }
449        }
450        '×' => Prim {
451            name: "×",
452            monad: M::Scalar(SM::Signum),
453            dyad: D::Scalar(SD::Mul),
454            ranks: [0, 0, 0],
455        },
456        '÷' => Prim {
457            name: "÷",
458            monad: M::Scalar(SM::Recip),
459            dyad: D::Scalar(SD::DivApl),
460            ranks: [0, 0, 0],
461        },
462        '⌈' => Prim {
463            name: "⌈",
464            monad: M::Scalar(SM::Ceil),
465            dyad: D::Scalar(SD::Max),
466            ranks: [0, 0, 0],
467        },
468        '⌊' => Prim {
469            name: "⌊",
470            monad: M::Scalar(SM::Floor),
471            dyad: D::Scalar(SD::Min),
472            ranks: [0, 0, 0],
473        },
474        '*' => {
475            Prim { name: "*", monad: M::Scalar(SM::Exp), dyad: D::Scalar(SD::Pow), ranks: [0, 0, 0] }
476        }
477        '|' => Prim {
478            name: "|",
479            monad: M::Scalar(SM::Abs),
480            dyad: D::Scalar(SD::Residue),
481            ranks: [0, 0, 0],
482        },
483        '=' => Prim { name: "=", monad: M::None, dyad: D::Scalar(SD::Eq), ranks: [0, 0, 0] },
484        '≠' => Prim {
485            name: "≠",
486            monad: M::NubSieve,
487            dyad: D::Scalar(SD::Ne),
488            ranks: [RANK_INF, 0, 0],
489        },
490        '<' => Prim { name: "<", monad: M::None, dyad: D::Scalar(SD::Lt), ranks: [0, 0, 0] },
491        '≤' => Prim { name: "≤", monad: M::None, dyad: D::Scalar(SD::Le), ranks: [0, 0, 0] },
492        '>' => Prim { name: ">", monad: M::None, dyad: D::Scalar(SD::Gt), ranks: [0, 0, 0] },
493        '≥' => Prim { name: "≥", monad: M::None, dyad: D::Scalar(SD::Ge), ranks: [0, 0, 0] },
494        '⍴' => Prim {
495            name: "⍴",
496            monad: M::ShapeOf,
497            dyad: D::Reshape,
498            ranks: [RANK_INF, 1, RANK_INF],
499        },
500        '⍳' => Prim {
501            name: "⍳",
502            monad: M::IotaApl { origin },
503            dyad: D::IndexOf { origin, vector_left: d.lookup_left == LookupLeft::VectorOnly },
504            // The monad takes the whole argument: a vector of lengths asks
505            // for a nested index array, which is a refusal, not a frame of
506            // one index generator per atom.
507            ranks: [RANK_INF, RANK_INF, RANK_INF],
508        },
509        '∊' => Prim {
510            name: "∊",
511            monad: M::Enlist,
512            dyad: D::MemberApl,
513            ranks: [RANK_INF, RANK_INF, RANK_INF],
514        },
515        '∪' => Prim {
516            name: "∪",
517            monad: M::Nub,
518            dyad: D::Union,
519            ranks: [RANK_INF, RANK_INF, RANK_INF],
520        },
521        '∩' => Prim {
522            name: "∩",
523            monad: M::None,
524            dyad: D::Intersect,
525            ranks: [RANK_INF, RANK_INF, RANK_INF],
526        },
527        '∧' => Prim { name: "∧", monad: M::None, dyad: D::Scalar(SD::Lcm), ranks: [0, 0, 0] },
528        '∨' => Prim { name: "∨", monad: M::None, dyad: D::Scalar(SD::Gcd), ranks: [0, 0, 0] },
529        '⍱' => Prim {
530            name: "⍱",
531            monad: M::None,
532            dyad: D::Boolean(BoolDyad::Nor),
533            ranks: [0, 0, 0],
534        },
535        '⍲' => Prim {
536            name: "⍲",
537            monad: M::None,
538            dyad: D::Boolean(BoolDyad::Nand),
539            ranks: [0, 0, 0],
540        },
541        '⍟' => Prim {
542            name: "⍟",
543            monad: M::Scalar(SM::Ln),
544            dyad: D::Scalar(SD::Log),
545            ranks: [0, 0, 0],
546        },
547        '~' => Prim {
548            name: "~",
549            monad: M::Scalar(SM::Not),
550            dyad: D::Less,
551            ranks: [0, RANK_INF, RANK_INF],
552        },
553        '≡' => Prim {
554            name: "≡",
555            monad: M::Depth { signed: d.depth_sign == DepthSign::Signed },
556            dyad: D::Match,
557            ranks: [RANK_INF, RANK_INF, RANK_INF],
558        },
559        '⍋' => Prim {
560            name: "⍋",
561            monad: M::GradeUp { origin },
562            dyad: D::CollateGrade { down: false, origin },
563            ranks: [RANK_INF, RANK_INF, RANK_INF],
564        },
565        '⍒' => Prim {
566            name: "⍒",
567            monad: M::GradeDown { origin },
568            dyad: D::CollateGrade { down: true, origin },
569            ranks: [RANK_INF, RANK_INF, RANK_INF],
570        },
571        // `⊖` works on the leading axis; `⌽` is the same primitive applied to
572        // rows, which `verb_for` wraps in the rank that does it. The DYAD
573        // takes its argument whole in both spellings: APL's rotate reads
574        // one amount per vector along the axis it moves, and picking the
575        // axis is the primitive's own job.
576        '⊖' | '⌽' => Prim {
577            name: if ch == '⊖' { "⊖" } else { "⌽" },
578            monad: M::Reverse,
579            dyad: D::RotateApl { last: ch == '⌽' },
580            ranks: [RANK_INF, RANK_INF, RANK_INF],
581        },
582        '⍪' => Prim {
583            name: "⍪",
584            monad: M::TableOf,
585            dyad: D::AppendLeading,
586            ranks: [RANK_INF, RANK_INF, RANK_INF],
587        },
588        '!' => Prim {
589            name: "!",
590            monad: M::Scalar(SM::Factorial),
591            dyad: D::Scalar(SD::Binomial),
592            ranks: [0, 0, 0],
593        },
594        '⍕' => Prim {
595            name: "⍕",
596            monad: M::Format,
597            dyad: D::FormatSpec,
598            ranks: [RANK_INF, 1, RANK_INF],
599        },
600        // `⊥` and `⊤` have no monadic meaning in APL; J spells those `#.`
601        // and `#:`. Both take their arguments whole: `⊥` is the inner
602        // product `+.×` over x's last axis and y's leading one, and `⊤`
603        // makes x's leading axis the digits, so the result of either is
604        // shaped by what is left of both arguments.
605        '⊥' => Prim {
606            name: "⊥",
607            monad: M::None,
608            dyad: D::DecodeApl,
609            ranks: [RANK_INF, RANK_INF, RANK_INF],
610        },
611        '⊤' => Prim {
612            name: "⊤",
613            monad: M::None,
614            dyad: D::EncodeApl,
615            ranks: [RANK_INF, RANK_INF, RANK_INF],
616        },
617        '⍉' => Prim {
618            name: "⍉",
619            monad: M::TransposeAxes,
620            dyad: D::TransposeApl,
621            ranks: [RANK_INF, RANK_INF, RANK_INF],
622        },
623        // `↑` and `⊃` are the lineages' clearest divergence, so the
624        // dialect names which reading applies; the dyads agree.
625        '↑' => Prim {
626            name: "↑",
627            monad: match d.first_disclose {
628                FirstDisclose::UpIsFirst => M::First,
629                FirstDisclose::UpIsMix => M::Open,
630            },
631            // Mix is the rank-0 application: every item opened, and the
632            // results framed into one array. First sees the whole array.
633            ranks: match d.first_disclose {
634                FirstDisclose::UpIsFirst => [RANK_INF, 1, RANK_INF],
635                FirstDisclose::UpIsMix => [0, 1, RANK_INF],
636            },
637            dyad: D::Take,
638        },
639        '⊂' => Prim {
640            name: "⊂",
641            // A floating model cannot nest a simple scalar, so `⊂3` is 3;
642            // a grounded one encloses it like anything else.
643            monad: match d.nested_model {
644                NestedModel::Floating => M::Enclose(Enclose::ExceptSimpleScalar),
645                NestedModel::Grounded => return None,
646            },
647            // The flag reading is the partition; the count reading is
648            // Dyalog's partitioned enclose, a different function.
649            dyad: match d.partition {
650                Partition::Flags => D::PartitionEnclose,
651                Partition::Counts => D::PartitionCounts,
652            },
653            ranks: [RANK_INF, RANK_INF, RANK_INF],
654        },
655        // Dyalog's `⊆`: nest monadically, and the partition GNU APL spells
656        // `⊂` dyadically. GNU APL has neither, so both follow Dyalog.
657        '⊆' => Prim {
658            name: "⊆",
659            monad: M::Nest,
660            dyad: D::PartitionEnclose,
661            ranks: [RANK_INF, RANK_INF, RANK_INF],
662        },
663        // `⍸` counts from ⎕IO; its dyad is the interval index, which GNU
664        // APL answers with the count of bounds below the value plus ⎕IO-1.
665        '⍸' => Prim {
666            name: "⍸",
667            monad: M::Indices { origin, boxed_coords: true },
668            dyad: D::IntervalIndex { offset: origin - 1, closed: true },
669            ranks: [RANK_INF, 1, RANK_INF],
670        },
671        // `⌷` indexes with one scalar per axis and has no monadic case in
672        // the APL2 reading; the other reads index vectors instead.
673        '⌷' => Prim {
674            name: "⌷",
675            // Monadically `⌷` materialises, which for an array libjay
676            // already holds is the array itself, in both readings.
677            monad: M::Same,
678            dyad: D::Squad { origin, leading: d.index_form == IndexForm::AxisVectors },
679            ranks: [RANK_INF, RANK_INF, RANK_INF],
680        },
681        '?' => Prim {
682            name: "?",
683            monad: M::Roll { origin, fixed: false, float_at_zero: false },
684            dyad: D::Deal { origin, fixed: false },
685            ranks: [RANK_INF, 0, 0],
686        },
687        '⌹' => Prim {
688            name: "⌹",
689            monad: M::MatrixInverse,
690            dyad: D::MatrixDivide,
691            ranks: [2, RANK_INF, 2],
692        },
693        '⊃' => Prim {
694            name: "⊃",
695            monad: match d.first_disclose {
696                FirstDisclose::UpIsFirst => M::Open,
697                FirstDisclose::UpIsMix => M::First,
698            },
699            dyad: D::Pick { origin },
700            ranks: match d.first_disclose {
701                FirstDisclose::UpIsFirst => [0, RANK_INF, RANK_INF],
702                FirstDisclose::UpIsMix => [RANK_INF, RANK_INF, RANK_INF],
703            },
704        },
705        '↓' => Prim {
706            name: "↓",
707            monad: M::Split,
708            dyad: D::Drop,
709            ranks: [RANK_INF, 1, RANK_INF],
710        },
711        ',' => Prim {
712            name: ",",
713            monad: M::Ravel,
714            dyad: D::AppendLast,
715            ranks: [RANK_INF, RANK_INF, RANK_INF],
716        },
717        '≢' => Prim {
718            name: "≢",
719            monad: M::Tally,
720            dyad: D::NotMatch,
721            ranks: [RANK_INF, RANK_INF, RANK_INF],
722        },
723        '⊢' => Prim {
724            name: "⊢",
725            monad: M::Same,
726            dyad: D::Right,
727            ranks: [RANK_INF, RANK_INF, RANK_INF],
728        },
729        '⊣' => Prim {
730            name: "⊣",
731            monad: M::Same,
732            dyad: D::Left,
733            ranks: [RANK_INF, RANK_INF, RANK_INF],
734        },
735        '○' => Prim {
736            name: "○",
737            monad: M::Scalar(SM::Pi),
738            dyad: D::Scalar(SD::Circle),
739            ranks: [0, 0, 0],
740        },
741        '⍷' => Prim {
742            name: "⍷",
743            monad: M::None,
744            dyad: D::FindSeq,
745            ranks: [RANK_INF, RANK_INF, RANK_INF],
746        },
747        '⍎' => Prim {
748            name: "⍎",
749            monad: M::Execute { apl: true },
750            dyad: D::None,
751            ranks: [1, RANK_INF, RANK_INF],
752        },
753        _ => return None,
754    };
755    Some(p)
756}
757
758/// The function a glyph denotes. Every glyph but `⌽` is a bare primitive;
759/// `⌽` is `⊖` applied to rows, so it carries the rank that does that: cells
760/// of rank 1 on the right, atoms on the left.
761fn verb_for(ch: char, d: Rules) -> Option<Verb> {
762    let p = prim_for(ch, d)?;
763    // Monadic `⌽` is `⊖` on rows, which the rank operator supplies. The
764    // dyad picks its own axis, so it keeps its whole arguments.
765    if ch == '⌽' {
766        return Some(Verb::Rank(Box::new(Verb::Prim(p)), [1, RANK_INF, RANK_INF]));
767    }
768    Some(Verb::Prim(p))
769}
770
771/// A `⎕`-name: the pure ones libjay answers, and a clear refusal for the
772/// ones that would have to reach outside the sandbox.
773///
774/// `⎕IO` and `⎕CT` are the dialect's own settings, readable but not
775/// assignable — the compiler fixed them before the program ran.
776fn quad_name(name: &str, d: Rules, span: Span) -> Result<Tok> {
777    let chars = |s: &str| Tok::Value(Array::from_chars(s.chars().collect()));
778    Ok(match name {
779        "A" => chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
780        "D" => chars("0123456789"),
781        "IO" => Tok::Value(Array::scalar_i64(d.origin)),
782        "CT" => Tok::Value(Array::scalar_f64(d.ct)),
783        "FX" => Tok::QuadFx,
784        "UCS" => Tok::Func(Verb::Prim(Prim {
785            name: "⎕UCS",
786            monad: MonadOp::Unicode { pass_chars: false },
787            dyad: DyadOp::None,
788            ranks: [RANK_INF, RANK_INF, RANK_INF],
789        })),
790        // The ones that would read a clock, a workspace or a file. The
791        // sandbox is libjay's own policy, not a queue position, so this
792        // is a refusal and not a promise.
793        "TS" | "AI" | "TC" | "WA" | "SI" | "LC" | "NL" | "EX" | "FIO" | "NA" | "SH" | "CMD"
794        | "MAP" | "SVO" | "SVQ" | "TZ" | "DL" => {
795            Err(Error::sandbox(format!("⎕{name} reads outside the program"), span))?
796        }
797        other => Err(Error::not_yet(format!("the system name ⎕{other}"), span))?,
798    })
799}
800
801/// A glyph the language has and libjay does not, with the name to report
802/// it under. These are queue positions, not unknown characters.
803fn queued_glyph(ch: char) -> Option<&'static str> {
804    Some(match ch {
805        '⌶' => "I-beam (⌶)",
806        // Dyalog's spawn. Named as a queue position rather than reported as
807        // an unknown character; whether libjay's sandbox opens APL's
808        // threads is the decision that has not been made, not the parsing.
809        '&' => "the spawn operator (f&y)",
810        _ => return None,
811    })
812}
813
814fn op_for(ch: char) -> Option<OpGlyph> {
815    match ch {
816        '/' => Some(OpGlyph::Slash),
817        '⌿' => Some(OpGlyph::SlashBar),
818        '\\' => Some(OpGlyph::Backslash),
819        '⍀' => Some(OpGlyph::BackslashBar),
820        '⍤' => Some(OpGlyph::Rank),
821        '⍨' => Some(OpGlyph::Commute),
822        '⍣' => Some(OpGlyph::Power),
823        '∘' => Some(OpGlyph::Jot),
824        '⍥' => Some(OpGlyph::Over),
825        '⍢' => Some(OpGlyph::Under),
826        '⌺' => Some(OpGlyph::Stencil),
827        '¨' => Some(OpGlyph::Each),
828        '⍛' => Some(OpGlyph::Before),
829        '⌸' => Some(OpGlyph::Key),
830        '⍠' => Some(OpGlyph::Variant),
831        // A `.` that is not the start of a number and not the tail of `∘.`
832        // is the inner-product operator.
833        '.' => Some(OpGlyph::Dot),
834        _ => None,
835    }
836}
837
838/// Expand as a function: `x\\y` along the last axis, `x⍀y` along the
839/// leading one, matching the two axes replicate distinguishes.
840fn expand_verb(leading: bool) -> Verb {
841    let p = Prim {
842        name: if leading { "⍀" } else { "\\" },
843        monad: MonadOp::None,
844        dyad: DyadOp::Expand,
845        ranks: if leading { [RANK_INF, 1, RANK_INF] } else { [RANK_INF, 1, 1] },
846    };
847    Verb::Prim(p)
848}
849
850/// Replicate as a function: `x/y` along the last axis, `x⌿y` along the
851/// leading one. One primitive, applied at the rank that picks the axis —
852/// exactly the J/APL divergence the shared IR is built to carry.
853fn copy_verb(leading: bool) -> Verb {
854    let p = Prim {
855        name: if leading { "⌿" } else { "/" },
856        monad: MonadOp::None,
857        dyad: DyadOp::Copy,
858        ranks: if leading { [RANK_INF, 1, RANK_INF] } else { [RANK_INF, 1, 1] },
859    };
860    Verb::Prim(p)
861}
862
863// ---------------------------------------------------------------------------
864// Lexer
865// ---------------------------------------------------------------------------
866
867/// Split the source into sentences of tokens. Blank sentences are dropped.
868/// Spans are absolute offsets into `SourceParts::display`.
869fn lex(src: &SourceParts, d: Rules) -> Result<Vec<Vec<Token>>> {
870    let mut out: Vec<Vec<Token>> = Vec::new();
871    let mut cur: Vec<Token> = Vec::new();
872    // A comment runs to the end of a line, which may be a later segment.
873    let mut in_comment = false;
874    let mut braces = 0usize;
875    for seg in &src.segments {
876        match seg {
877            Segment::Text { text, offset } => {
878                lex_text(text, *offset, d, &mut out, &mut cur, &mut in_comment, &mut braces)?;
879            }
880            Segment::Param { index, offset, len } => {
881                if !in_comment {
882                    cur.push(Token {
883                        kind: Tok::Param(*index),
884                        span: Span::new(*offset, offset + len),
885                    });
886                }
887            }
888        }
889    }
890    if !cur.is_empty() {
891        out.push(cur);
892    }
893    Ok(out)
894}
895
896#[allow(clippy::too_many_arguments)]
897fn lex_text(
898    text: &str,
899    offset: usize,
900    d: Rules,
901    out: &mut Vec<Vec<Token>>,
902    cur: &mut Vec<Token>,
903    in_comment: &mut bool,
904    braces: &mut usize,
905) -> Result<()> {
906    let mut i = 0usize;
907    while i < text.len() {
908        let ch = text[i..].chars().next().unwrap();
909        let clen = ch.len_utf8();
910        if *in_comment {
911            if ch == '\n' {
912                *in_comment = false;
913                end_sentence(out, cur);
914            }
915            i += clen;
916            continue;
917        }
918        match ch {
919            // Inside a dfn's braces neither a line break nor `⋄` ends the
920            // sentence the dfn belongs to; both separate its statements.
921            '\n' | '⋄' => {
922                if *braces > 0 {
923                    cur.push(Token {
924                        kind: Tok::Separator,
925                        span: Span::new(offset + i, offset + i + clen),
926                    });
927                } else {
928                    end_sentence(out, cur);
929                }
930                i += clen;
931            }
932            ' ' | '\t' | '\r' => i += clen,
933            '⍝' => {
934                *in_comment = true;
935                i += clen;
936            }
937            '\'' => {
938                let (arr, next) = lex_string(text, i, offset)?;
939                cur.push(Token {
940                    kind: Tok::Value(arr),
941                    span: Span::new(offset + i, offset + next),
942                });
943                i = next;
944            }
945            '{' => {
946                *braces += 1;
947                cur.push(Token { kind: Tok::LBrace, span: Span::new(offset + i, offset + i + 1) });
948                i += 1;
949            }
950            '}' => {
951                *braces = braces.saturating_sub(1);
952                cur.push(Token { kind: Tok::RBrace, span: Span::new(offset + i, offset + i + 1) });
953                i += 1;
954            }
955            '∇' => {
956                cur.push(Token { kind: Tok::Del, span: Span::new(offset + i, offset + i + clen) });
957                i += clen;
958            }
959            // `⍺⍺` and `⍵⍵` are one name each: a dfn operator's operands.
960            '⍺' | '⍵' => {
961                let mut end = i + clen;
962                if text[end..].starts_with(ch) {
963                    end += clen;
964                }
965                cur.push(Token {
966                    kind: Tok::Name(text[i..end].to_string()),
967                    span: Span::new(offset + i, offset + end),
968                });
969                i = end;
970            }
971            // `:If` and its family are one word; a bare `:` is a dfn guard.
972            // A dfn has no control words at all, so inside braces the `:`
973            // is a guard however its condition's answer is spelled —
974            // `a>10:a` names no control word and never could.
975            ':' if *braces > 0 => {
976                cur.push(Token {
977                    kind: Tok::Colon,
978                    span: Span::new(offset + i, offset + i + 1),
979                });
980                i += 1;
981            }
982            ':' => {
983                let mut j = i + 1;
984                while let Some(c) = text[j..].chars().next() {
985                    if c.is_ascii_alphabetic() {
986                        j += c.len_utf8();
987                    } else {
988                        break;
989                    }
990                }
991                let span = Span::new(offset + i, offset + j);
992                match control_word(&text[i + 1..j]) {
993                    Some(word) => cur.push(Token { kind: Tok::Control(word), span }),
994                    None if j > i + 1 => {
995                        return Err(Error::parse(
996                            format!("unknown control word: {}", &text[i..j]),
997                            span,
998                        ));
999                    }
1000                    None => cur.push(Token {
1001                        kind: Tok::Colon,
1002                        span: Span::new(offset + i, offset + i + 1),
1003                    }),
1004                }
1005                i = j;
1006            }
1007            '→' => {
1008                cur.push(Token {
1009                    kind: Tok::Arrow,
1010                    span: Span::new(offset + i, offset + i + clen),
1011                });
1012                i += clen;
1013            }
1014            // `⍬` is the empty numeric vector, written as a constant.
1015            '⍬' => {
1016                cur.push(Token {
1017                    kind: Tok::Value(Array::empty(crate::dtype::DType::I64)),
1018                    span: Span::new(offset + i, offset + i + clen),
1019                });
1020                i += clen;
1021            }
1022            '(' => {
1023                cur.push(Token { kind: Tok::LParen, span: Span::new(offset + i, offset + i + 1) });
1024                i += 1;
1025            }
1026            ')' => {
1027                cur.push(Token { kind: Tok::RParen, span: Span::new(offset + i, offset + i + 1) });
1028                i += 1;
1029            }
1030            '[' => {
1031                cur.push(Token {
1032                    kind: Tok::LBracket,
1033                    span: Span::new(offset + i, offset + i + 1),
1034                });
1035                i += 1;
1036            }
1037            ']' => {
1038                cur.push(Token {
1039                    kind: Tok::RBracket,
1040                    span: Span::new(offset + i, offset + i + 1),
1041                });
1042                i += 1;
1043            }
1044            ';' => {
1045                cur.push(Token { kind: Tok::Semi, span: Span::new(offset + i, offset + i + 1) });
1046                i += 1;
1047            }
1048            '←' => {
1049                cur.push(Token {
1050                    kind: Tok::Assign,
1051                    span: Span::new(offset + i, offset + i + clen),
1052                });
1053                i += clen;
1054            }
1055            // `⍞` has no system-name form: it is always the whole token.
1056            '⍞' => {
1057                cur.push(Token {
1058                    kind: Tok::Quad { quote: true },
1059                    span: Span::new(offset + i, offset + i + clen),
1060                });
1061                i += clen;
1062            }
1063            '⎕' => {
1064                let after = i + clen;
1065                let mut j = after;
1066                while let Some(c) = text[j..].chars().next() {
1067                    if c.is_alphabetic() {
1068                        j += c.len_utf8();
1069                    } else {
1070                        break;
1071                    }
1072                }
1073                if j > after {
1074                    let span = Span::new(offset + i, offset + j);
1075                    let name = text[after..j].to_uppercase();
1076                    // Every system name libjay answers is read-only: the
1077                    // ones that are settings were fixed by the dialect
1078                    // before the program was compiled, so assigning one is
1079                    // a refusal and not a promise. A name libjay does not
1080                    // answer at all reports itself first.
1081                    if text[j..].trim_start().starts_with('←') {
1082                        quad_name(&name, d, span)?;
1083                        return Err(Error::language(
1084                            format!(
1085                                "⎕{name} is read-only: libjay's system names are \
1086                                 fixed before the program runs"
1087                            ),
1088                            span,
1089                        ));
1090                    }
1091                    cur.push(Token { kind: quad_name(&name, d, span)?, span });
1092                    i = j;
1093                    continue;
1094                }
1095                cur.push(Token {
1096                    kind: Tok::Quad { quote: false },
1097                    span: Span::new(offset + i, offset + after),
1098                });
1099                i = after;
1100            }
1101            _ if num_start(text, i) => {
1102                let (tok, next) = lex_number_vector(text, i, offset)?;
1103                cur.push(tok);
1104                i = next;
1105            }
1106            _ if is_name_start(ch) => {
1107                let start = i;
1108                i += clen;
1109                while let Some(c) = text[i..].chars().next() {
1110                    if is_name_body(c) {
1111                        i += c.len_utf8();
1112                    } else {
1113                        break;
1114                    }
1115                }
1116                cur.push(Token {
1117                    kind: Tok::Name(text[start..i].to_string()),
1118                    span: Span::new(offset + start, offset + i),
1119                });
1120            }
1121            _ => {
1122                let mut end = i + clen;
1123                if let Some(v) = verb_for(ch, d) {
1124                    cur.push(Token {
1125                        kind: Tok::Func(v),
1126                        span: Span::new(offset + i, offset + end),
1127                    });
1128                } else if let Some(mut op) = op_for(ch) {
1129                    // `∘.` is one operator (the outer product); a bare `∘`
1130                    // is Dyalog's compose, a different thing.
1131                    if op == OpGlyph::Jot && text[end..].starts_with('.') {
1132                        op = OpGlyph::JotDot;
1133                        end += 1;
1134                    }
1135                    cur.push(Token {
1136                        kind: Tok::Op(op),
1137                        span: Span::new(offset + i, offset + end),
1138                    });
1139                } else if let Some(what) = queued_glyph(ch) {
1140                    // A glyph of the language libjay has not reached yet is
1141                    // a promise, not an unknown character, and says so.
1142                    return Err(Error::not_yet(what, Span::new(offset + i, offset + end)));
1143                } else {
1144                    return Err(Error::parse(
1145                        format!("unknown symbol: {ch}"),
1146                        Span::new(offset + i, offset + end),
1147                    ));
1148                }
1149                i = end;
1150            }
1151        }
1152    }
1153    Ok(())
1154}
1155
1156fn end_sentence(out: &mut Vec<Vec<Token>>, cur: &mut Vec<Token>) {
1157    if !cur.is_empty() {
1158        out.push(std::mem::take(cur));
1159    }
1160}
1161
1162fn is_name_start(c: char) -> bool {
1163    c.is_alphabetic() || c == '∆' || c == '⍙'
1164}
1165
1166fn is_name_body(c: char) -> bool {
1167    c.is_alphanumeric() || c == '_' || c == '∆' || c == '⍙'
1168}
1169
1170/// `'...'` with `''` for an embedded quote. A one-character string is a
1171/// scalar; anything else is a vector.
1172fn lex_string(text: &str, start: usize, offset: usize) -> Result<(Array, usize)> {
1173    let mut chars: Vec<char> = Vec::new();
1174    let mut i = start + 1;
1175    loop {
1176        let c = match text[i..].chars().next() {
1177            Some(c) => c,
1178            None => {
1179                return Err(Error::parse(
1180                    "unterminated string",
1181                    Span::new(offset + start, offset + text.len()),
1182                ));
1183            }
1184        };
1185        if c == '\'' {
1186            if text[i + 1..].starts_with('\'') {
1187                chars.push('\'');
1188                i += 2;
1189                continue;
1190            }
1191            i += 1;
1192            break;
1193        }
1194        chars.push(c);
1195        i += c.len_utf8();
1196    }
1197    let shape = if chars.len() == 1 { vec![] } else { vec![chars.len()] };
1198    Ok((Array::new(shape, Data::Char(chars.into())), i))
1199}
1200
1201/// True if a numeric literal starts at byte `i`.
1202fn num_start(text: &str, i: usize) -> bool {
1203    let s = match text.get(i..) {
1204        Some(s) => s,
1205        None => return false,
1206    };
1207    let mut cs = s.chars();
1208    let c0 = match cs.next() {
1209        Some(c) => c,
1210        None => return false,
1211    };
1212    if c0.is_ascii_digit() {
1213        return true;
1214    }
1215    if c0 == '.' {
1216        return cs.next().is_some_and(|d| d.is_ascii_digit());
1217    }
1218    if c0 == '¯' {
1219        return match cs.next() {
1220            Some(d) if d.is_ascii_digit() => true,
1221            Some('.') => cs.next().is_some_and(|d| d.is_ascii_digit()),
1222            _ => false,
1223        };
1224    }
1225    false
1226}
1227
1228/// One numeric literal. Returns its value, the machine integer it is
1229/// exactly (None where it needs floating point), and the byte index just
1230/// past it.
1231///
1232/// The exact integer is not `value as i64`: every i64 above 2^53 rounds
1233/// when it passes through a double, and `9223372036854775806⌽1 2 3` is a
1234/// sentence the language answers.
1235fn lex_number(text: &str, start: usize, offset: usize) -> Result<(f64, Option<i64>, usize)> {
1236    let mut i = start;
1237    let mut buf = String::new();
1238    let mut saw_dot = false;
1239    if text[i..].starts_with('¯') {
1240        buf.push('-');
1241        i += '¯'.len_utf8();
1242    }
1243    i = take_digits(text, i, &mut buf);
1244    if text[i..].starts_with('.') && text[i + 1..].chars().next().is_some_and(|d| d.is_ascii_digit())
1245    {
1246        saw_dot = true;
1247        buf.push('.');
1248        i += 1;
1249        i = take_digits(text, i, &mut buf);
1250    }
1251    if let Some(c) = text[i..].chars().next() && (c == 'e' || c == 'E') {
1252        let after = i + 1;
1253        let neg = text[after..].starts_with('¯');
1254        let digits_at = if neg { after + '¯'.len_utf8() } else { after };
1255        if text[digits_at..].chars().next().is_some_and(|d| d.is_ascii_digit()) {
1256            buf.push('e');
1257            if neg {
1258                buf.push('-');
1259            }
1260            i = take_digits(text, digits_at, &mut buf);
1261        }
1262    }
1263    let v: f64 = buf.parse().map_err(|_| {
1264        Error::parse(
1265            format!("cannot read the number {}", &text[start..i]),
1266            Span::new(offset + start, offset + i),
1267        )
1268    })?;
1269    // `1e3` is the integer 1000; `1e¯3` and `2.5` are floats. Digits alone
1270    // are read as an integer straight from the text, so a value the double
1271    // cannot hold exactly still arrives exact.
1272    let exact = if saw_dot {
1273        None
1274    } else if let Ok(k) = buf.parse::<i64>() {
1275        Some(k)
1276    } else if v.fract() == 0.0 && v.abs() < 9.0e18 {
1277        Some(v as i64)
1278    } else {
1279        None
1280    };
1281    Ok((v, exact, i))
1282}
1283
1284fn take_digits(text: &str, mut i: usize, buf: &mut String) -> usize {
1285    while let Some(c) = text[i..].chars().next() {
1286        if c.is_ascii_digit() {
1287            buf.push(c);
1288            i += 1;
1289        } else {
1290            break;
1291        }
1292    }
1293    i
1294}
1295
1296/// A run of blank-separated numeric literals: one value token. Integers
1297/// unless some literal needs floating point; a single literal is a scalar.
1298fn lex_number_vector(text: &str, start: usize, offset: usize) -> Result<(Token, usize)> {
1299    let mut vals: Vec<crate::complex::Cx> = Vec::new();
1300    let mut exacts: Vec<i64> = Vec::new();
1301    let mut any_float = false;
1302    let mut any_complex = false;
1303    let mut i = start;
1304    let mut end;
1305    loop {
1306        let (v, exact, mut next) = lex_number(text, i, offset)?;
1307        let mut imag = 0.0;
1308        if let Some(c) = text[next..].chars().next() {
1309            // `3J4` is the rectangular form; `J` is not otherwise a
1310            // character a numeric literal can continue with.
1311            if (c == 'j' || c == 'J') && num_start(text, next + 1) {
1312                let (b, _, imag_end) = lex_number(text, next + 1, offset)?;
1313                imag = b;
1314                next = imag_end;
1315                any_complex = true;
1316            }
1317        }
1318        vals.push([v, imag]);
1319        match exact {
1320            Some(k) => exacts.push(k),
1321            None => any_float = true,
1322        }
1323        end = next;
1324        i = next;
1325        let mut k = i;
1326        while text[k..].starts_with(' ') || text[k..].starts_with('\t') {
1327            k += 1;
1328        }
1329        if k > i && num_start(text, k) {
1330            i = k;
1331            continue;
1332        }
1333        break;
1334    }
1335    let data = if any_complex {
1336        Data::Complex(vals.into())
1337    } else if any_float {
1338        Data::F64(vals.iter().map(|&v| v[0]).collect())
1339    } else {
1340        Data::I64(exacts.into())
1341    };
1342    let shape = if data.len() == 1 { vec![] } else { vec![data.len()] };
1343    let tok = Token {
1344        kind: Tok::Nums(Array::new(shape, data)),
1345        span: Span::new(offset + start, offset + end),
1346    };
1347    Ok((tok, end))
1348}
1349
1350// ---------------------------------------------------------------------------
1351// Operator folding
1352// ---------------------------------------------------------------------------
1353
1354/// `A∘f` and `f∘A`: the array bound where the other operand belongs, or
1355/// None where both sides are functions and the ordinary composition runs.
1356///
1357/// The array has to be a LITERAL. A computed operand — `(⍳3)∘+` — is a
1358/// named gap rather than a wrong answer: it would have to be evaluated
1359/// when the derived function is built, and nothing in the IR holds an
1360/// operand's expression yet.
1361fn bind_value(
1362    it: &mut std::iter::Peekable<std::vec::IntoIter<Token>>,
1363    out: &mut Vec<Token>,
1364) -> Option<Token> {
1365    let left_is_value = out.last().map(|t| &t.kind).and_then(literal).is_some();
1366    let left_is_func = matches!(out.last().map(|t| &t.kind), Some(Tok::Func(_)));
1367    let right_is_value = it.peek().map(|t| &t.kind).and_then(literal).is_some();
1368    let right_is_func = matches!(it.peek().map(|t| &t.kind), Some(Tok::Func(_)));
1369    if !((left_is_value && right_is_func) || (left_is_func && right_is_value)) {
1370        return None;
1371    }
1372    let ltok = out.pop().expect("checked above");
1373    let rtok = it.next().expect("peeked");
1374    let span = Span::merge(ltok.span, rtok.span);
1375    // `A∘f y` is `A f y`; `f∘A y` is `y f A`.
1376    let derived = if left_is_value {
1377        let Tok::Func(g) = rtok.kind else { unreachable!("checked above") };
1378        Verb::BondLeft(literal(&ltok.kind).expect("checked above").clone(), Box::new(g))
1379    } else {
1380        let Tok::Func(f) = ltok.kind else { unreachable!("checked above") };
1381        Verb::BondRight(Box::new(f), literal(&rtok.kind).expect("checked above").clone())
1382    };
1383    Some(Token { kind: Tok::Func(derived), span })
1384}
1385
1386/// Fold monadic and dyadic operators into derived-function tokens, left to
1387/// right. After this the sentence holds only values, names, functions, `←`,
1388/// `⎕` and parentheses.
1389fn fold_operators(toks: Vec<Token>, d: Rules) -> Result<Vec<Token>> {
1390    let mut out: Vec<Token> = Vec::new();
1391    let mut it = toks.into_iter().peekable();
1392    while let Some(t) = it.next() {
1393        // Parentheses close here rather than in a pass of their own: by the
1394        // time the `)` arrives everything inside has been folded, so a pair
1395        // holding nothing but functions is a single function from here on
1396        // and the operator to its right binds to it.
1397        if matches!(t.kind, Tok::RParen) {
1398            out.push(t);
1399            close_paren(&mut out, d)?;
1400            continue;
1401        }
1402        // A dfn that mentions `⍺⍺` or `⍵⍵` is an operator: it takes the
1403        // function on its left, and one on its right where it asked for it.
1404        if let Tok::UserOp { def, omega } = &t.kind {
1405            let (def, omega) = (def.clone(), *omega);
1406            let right = if omega {
1407                match it.peek().map(|tok| &tok.kind) {
1408                    Some(Tok::Func(_)) => {
1409                        let g = it.next().expect("peeked");
1410                        let Tok::Func(g) = g.kind else { unreachable!("checked above") };
1411                        Some(Operand::Func(Box::new(g)))
1412                    }
1413                    // Dyalog lets an ARRAY stand where a function operand
1414                    // belongs, and the body reads `⍵⍵` as that array.
1415                    Some(k) if literal(k).is_some() => {
1416                        let a = it.next().expect("peeked");
1417                        Some(Operand::Value(Box::new(literal(&a.kind).expect("checked").clone())))
1418                    }
1419                    _ => {
1420                        return Err(Error::parse(
1421                            "⍵⍵ needs an operand on the operator's right",
1422                            t.span,
1423                        ));
1424                    }
1425                }
1426            } else {
1427                None
1428            };
1429            let alpha = match out.pop() {
1430                Some(Token { kind: Tok::Func(f), span }) => (Operand::Func(Box::new(f)), span),
1431                Some(tok) if literal(&tok.kind).is_some() => {
1432                    (Operand::Value(Box::new(literal(&tok.kind).expect("checked").clone())), tok.span)
1433                }
1434                _ => {
1435                    return Err(Error::parse(
1436                        "⍺⍺ needs an operand on the operator's left",
1437                        t.span,
1438                    ));
1439                }
1440            };
1441            let (alpha, fspan) = alpha;
1442            let derived = Verb::UserDerived { def, alpha, omega: right };
1443            out.push(Token { kind: Tok::Func(derived), span: Span::merge(fspan, t.span) });
1444            continue;
1445        }
1446        let op = match t.kind {
1447            Tok::Op(op) => op,
1448            _ => {
1449                out.push(t);
1450                continue;
1451            }
1452        };
1453        // The outer product is the one operator whose operand is on its
1454        // right; it derives the same table J spells `u/`.
1455        if op == OpGlyph::JotDot {
1456            let ftok = match it.peek() {
1457                Some(tok) if matches!(tok.kind, Tok::Func(_)) => it.next().unwrap(),
1458                _ => {
1459                    return Err(Error::parse("∘. needs a function on its right", t.span));
1460                }
1461            };
1462            let span = Span::merge(t.span, ftok.span);
1463            let Tok::Func(f) = ftok.kind else { unreachable!("checked above") };
1464            out.push(Token { kind: Tok::Func(Verb::Reduce(Box::new(f))), span });
1465            continue;
1466        }
1467        // `A∘f` and `f∘A` bind an array where the other operand belongs:
1468        // `A∘f` supplies it on the left (`2∘× 5` is `2×5`) and `f∘A` on
1469        // the right (`(÷∘2) 7` is `7÷2`). Both are monadic only, as J's
1470        // `m&v` and `u&n` are. The other compositions take functions.
1471        if op == OpGlyph::Jot
1472            && let Some(bound) = bind_value(&mut it, &mut out)
1473        {
1474            out.push(bound);
1475            continue;
1476        }
1477        // `f∘g` and `f⍥g` need a function on both sides; the right one is
1478        // taken here so the ordinary "operand to the left" path can run.
1479        if matches!(
1480            op,
1481            OpGlyph::Jot | OpGlyph::Over | OpGlyph::Before | OpGlyph::Under | OpGlyph::Dot
1482        ) {
1483            let Some(gtok) = it.peek().filter(|x| matches!(x.kind, Tok::Func(_))) else {
1484                return Err(Error::not_yet(
1485                    format!("{} with a value operand", op.glyph()),
1486                    t.span,
1487                ));
1488            };
1489            let gspan = gtok.span;
1490            let Some(Token { kind: Tok::Func(g), .. }) = it.next() else {
1491                unreachable!("peeked a function")
1492            };
1493            let Some(Token { kind: Tok::Func(f), span: fspan }) = out.pop() else {
1494                return Err(Error::not_yet(
1495                    format!("{} with a value operand", op.glyph()),
1496                    t.span,
1497                ));
1498            };
1499            let span = Span::merge(fspan, gspan);
1500            // Beside runs g on the right argument only; over runs it on
1501            // both. Neither is in GNU APL, so both follow Dyalog.
1502            let derived = match op {
1503                OpGlyph::Jot => Verb::Beside(Box::new(f), Box::new(g)),
1504                OpGlyph::Before => Verb::Before(Box::new(f), Box::new(g)),
1505                // Under is over, undone: the published definition is
1506                // `g⍣¯1 ⊢ (g x) f (g y)`, on the arguments whole, so it is
1507                // J's `&.:` over the same obverse table.
1508                OpGlyph::Under => {
1509                    let back = crate::verb::obverse(&g).ok_or_else(|| {
1510                        Error::not_yet(
1511                            format!("the obverse of {} (no inverse is known)", g.name()),
1512                            gspan,
1513                        )
1514                    })?;
1515                    let composed = Verb::Compose(Box::new(f), Box::new(g));
1516                    Verb::Atop(Box::new(back), Box::new(composed))
1517                }
1518                // `f.g` folds with f what g made of every row and column:
1519                // `+.×` is the matrix product, `∧.=` asks which rows match.
1520                OpGlyph::Dot => Verb::InnerProduct {
1521                    u: Box::new(Verb::Reduce(Box::new(f))),
1522                    v: Box::new(g),
1523                    apl: true,
1524                },
1525                _ => Verb::Compose(Box::new(f), Box::new(g)),
1526            };
1527            out.push(Token { kind: Tok::Func(derived), span });
1528            continue;
1529        }
1530        // Left operand: a function, derived or not.
1531        let left_is_func = matches!(out.last().map(|x| &x.kind), Some(Tok::Func(_)));
1532        if !left_is_func {
1533            // After an operand these glyphs are functions, not operators.
1534            // Names are always values in this subset, so the reading is
1535            // decided by the token to the left and nothing else.
1536            if out.last().is_some_and(|x| is_operand_end(&x.kind)) {
1537                let f = match op {
1538                    OpGlyph::Slash => copy_verb(false),
1539                    OpGlyph::SlashBar => copy_verb(true),
1540                    OpGlyph::Backslash => expand_verb(false),
1541                    OpGlyph::BackslashBar => expand_verb(true),
1542                    OpGlyph::Rank
1543                    | OpGlyph::Commute
1544                    | OpGlyph::Power
1545                    | OpGlyph::JotDot
1546                    | OpGlyph::Jot
1547                    | OpGlyph::Over
1548                    | OpGlyph::Under
1549                    | OpGlyph::Stencil
1550                    | OpGlyph::Before
1551                    | OpGlyph::Key
1552                    | OpGlyph::Dot
1553                    | OpGlyph::Variant
1554                    | OpGlyph::Each => {
1555                        return Err(Error::parse(
1556                            format!("{} needs a function to its left", op.glyph()),
1557                            t.span,
1558                        ));
1559                    }
1560                };
1561                out.push(Token { kind: Tok::Func(f), span: t.span });
1562                continue;
1563            }
1564            return Err(Error::parse(
1565                format!("{} needs a function to its left", op.glyph()),
1566                t.span,
1567            ));
1568        }
1569        let ftok = out.pop().unwrap();
1570        let f = match ftok.kind {
1571            Tok::Func(f) => f,
1572            _ => unreachable!("checked above"),
1573        };
1574        let span = Span::merge(ftok.span, t.span);
1575        // An explicit axis replaces the glyph's own choice of one: `+/[k]`
1576        // and `+⌿[k]` both reduce axis k, and `f\\[k]` and `f⍀[k]` both scan
1577        // it, which is what makes the two spellings the same function here.
1578        if let Some((k, aspan)) = take_axis(&mut it, d)? {
1579            let inner = match op {
1580                OpGlyph::Slash | OpGlyph::SlashBar => Verb::NWise(Box::new(f)),
1581                OpGlyph::Backslash | OpGlyph::BackslashBar => {
1582                    Verb::Windowed(Box::new(Verb::Reduce(Box::new(f))), WindowKind::Scan)
1583                }
1584                _ => {
1585                    return Err(Error::not_yet(
1586                        format!("axis specification for {}", op.glyph()),
1587                        aspan,
1588                    ));
1589                }
1590            };
1591            out.push(Token {
1592                kind: Tok::Func(Verb::AlongAxis(Box::new(inner), k)),
1593                span: Span::merge(span, aspan),
1594            });
1595            continue;
1596        }
1597        let derived = match op {
1598            // APL's divergence from J: `/` reduces the last axis, `⌿` the
1599            // leading one. `+/` sums rows, `+⌿` sums columns. The dyad is
1600            // the n-wise reduction, whose left argument is one number
1601            // however it is shaped, so the wrapper frames the right
1602            // argument alone.
1603            OpGlyph::Slash => Verb::Rank(Box::new(Verb::NWise(Box::new(f))), [1, RANK_INF, 1]),
1604            OpGlyph::SlashBar => Verb::NWise(Box::new(f)),
1605            // The scan follows the reduce: `\` along the last axis, `⍀`
1606            // along the leading one. The k-th element is the reduce of the
1607            // first k, which is the verb applied to the k-th prefix.
1608            OpGlyph::Backslash => Verb::Rank(
1609                Box::new(Verb::Windowed(Box::new(Verb::Reduce(Box::new(f))), WindowKind::Scan)),
1610                [1, 1, 1],
1611            ),
1612            OpGlyph::BackslashBar => {
1613                Verb::Windowed(Box::new(Verb::Reduce(Box::new(f))), WindowKind::Scan)
1614            }
1615            OpGlyph::Commute => Verb::Commute(Box::new(f)),
1616            OpGlyph::Key => Verb::KeyPairs(Box::new(f)),
1617            // `f⍠B`: one setting of the dialect overridden for this
1618            // application and no other.
1619            OpGlyph::Variant => {
1620                let (options, ospan) = variant_options(&mut it, t.span)?;
1621                let derived = variant(f, &options, Span::merge(span, ospan))?;
1622                out.push(Token { kind: Tok::Func(derived), span: Span::merge(span, ospan) });
1623                continue;
1624            }
1625            // `.` reached with no function on its right: `+.` alone is not
1626            // a function in either lineage.
1627            OpGlyph::Dot => {
1628                return Err(Error::parse("the inner product . needs a function on its right", t.span));
1629            }
1630            // Each: the function runs on the contents of every item and
1631            // its result goes back into an item. A simple scalar result
1632            // stays simple, which is APL's enclosure rule.
1633            OpGlyph::Each => Verb::Each(Box::new(f), Enclose::ExceptSimpleScalar),
1634            OpGlyph::Power => {
1635                let spec = match it.peek() {
1636                    // `f⍣g` iterates until `new g old` holds: `f⍣≡` is the
1637                    // fixed point, which is the spelling the reference uses.
1638                    Some(tok) if matches!(tok.kind, Tok::Func(_)) => {
1639                        let gtok = it.next().unwrap();
1640                        let Tok::Func(g) = gtok.kind else { unreachable!("checked above") };
1641                        let v = Verb::PowerUntil(Box::new(f), Box::new(g));
1642                        out.push(Token {
1643                            kind: Tok::Func(v),
1644                            span: Span::merge(span, gtok.span),
1645                        });
1646                        continue;
1647                    }
1648                    Some(tok) if literal(&tok.kind).is_some() => it.next().unwrap(),
1649                    _ => {
1650                        return Err(Error::not_yet("computed power (f⍣n)", t.span));
1651                    }
1652                };
1653                let arr = literal(&spec.kind).expect("checked above");
1654                let (p, inverse) = power_spec(arr, spec.span)?;
1655                // `f⍣¯n` runs f's inverse n times, over the same obverse
1656                // table J's `u^:_n` reads.
1657                let f = if inverse { crate::frontend::j::obverse_of(&f, span)? } else { f };
1658                let f = Verb::PowerN(Box::new(f), p);
1659                out.push(Token { kind: Tok::Func(f), span: Span::merge(span, spec.span) });
1660                continue;
1661            }
1662            // `f⌺w`: the window sizes are a value on the right, one per
1663            // leading axis. Dyalog also takes a two-row form giving the
1664            // movement; that is a named gap.
1665            OpGlyph::Stencil => {
1666                let Some(spec) = it.peek().filter(|t| literal(&t.kind).is_some()) else {
1667                    return Err(Error::parse(
1668                        "⌺ needs a window specification on its right",
1669                        t.span,
1670                    ));
1671                };
1672                let sspan = spec.span;
1673                let spec = it.next().expect("peeked a literal");
1674                let arr = literal(&spec.kind).expect("checked above");
1675                if arr.rank() > 1 {
1676                    return Err(Error::not_yet(
1677                        "a stencil with a movement row (f⌺(m⍪w))",
1678                        sspan,
1679                    ));
1680                }
1681                let sizes = arr
1682                    .to_i64_vec()
1683                    .ok_or_else(|| Error::domain("a stencil window is whole numbers", sspan))?;
1684                let v = Verb::Stencil(Box::new(f), sizes);
1685                out.push(Token { kind: Tok::Func(v), span: Span::merge(span, sspan) });
1686                continue;
1687            }
1688            OpGlyph::Rank => {
1689                let spec = match it.peek() {
1690                    // `f⍤g` with a function on the right is Dyalog's atop:
1691                    // monadically `f g y`, dyadically `f (x g y)`.
1692                    Some(tok) if matches!(tok.kind, Tok::Func(_)) => {
1693                        let gtok = it.next().unwrap();
1694                        let Tok::Func(g) = gtok.kind else { unreachable!("checked above") };
1695                        let v = Verb::Atop(Box::new(f), Box::new(g));
1696                        out.push(Token {
1697                            kind: Tok::Func(v),
1698                            span: Span::merge(span, gtok.span),
1699                        });
1700                        continue;
1701                    }
1702                    Some(tok) if literal(&tok.kind).is_some() => it.next().unwrap(),
1703                    _ => {
1704                        return Err(Error::parse(
1705                            "⍤ needs a rank specification on its right",
1706                            t.span,
1707                        ));
1708                    }
1709                };
1710                let arr = literal(&spec.kind).expect("checked above");
1711                let ranks = rank_spec(arr, spec.span)?;
1712                let f = Verb::Rank(Box::new(f), ranks);
1713                out.push(Token { kind: Tok::Func(f), span: Span::merge(span, spec.span) });
1714                continue;
1715            }
1716            // These are answered before the left operand is taken.
1717            OpGlyph::JotDot
1718            | OpGlyph::Jot
1719            | OpGlyph::Over
1720            | OpGlyph::Under
1721            | OpGlyph::Before => {
1722                unreachable!("handled above")
1723            }
1724        };
1725        out.push(Token { kind: Tok::Func(derived), span });
1726    }
1727    Ok(out)
1728}
1729
1730/// `[k]` immediately after an operator glyph, if it is there. The axis is
1731/// given in `⎕IO` origin and comes back as a zero-based one.
1732fn take_axis(
1733    it: &mut std::iter::Peekable<std::vec::IntoIter<Token>>,
1734    d: Rules,
1735) -> Result<Option<(usize, Span)>> {
1736    if !matches!(it.peek().map(|t| &t.kind), Some(Tok::LBracket)) {
1737        return Ok(None);
1738    }
1739    let open = it.next().expect("peeked");
1740    let spec = match it.next() {
1741        Some(tok) if literal(&tok.kind).is_some() => tok,
1742        Some(tok) => return Err(Error::not_yet("a computed axis (f[k])", tok.span)),
1743        None => return Err(Error::parse("unterminated axis specification", open.span)),
1744    };
1745    let close = match it.next() {
1746        Some(tok) if matches!(tok.kind, Tok::RBracket) => tok,
1747        _ => return Err(Error::parse("unterminated axis specification", open.span)),
1748    };
1749    let span = Span::merge(open.span, close.span);
1750    let arr = literal(&spec.kind).expect("checked above");
1751    let ints = arr
1752        .to_i64_vec()
1753        .ok_or_else(|| Error::parse("an axis must be a whole number", spec.span))?;
1754    let [k] = ints[..] else {
1755        return Err(Error::not_yet("several axes in one specification", spec.span));
1756    };
1757    let origin = d.origin;
1758    let k = k - origin;
1759    if k < 0 {
1760        return Err(Error::domain(format!("axis {} does not exist", k + origin), spec.span));
1761    }
1762    Ok(Some((k as usize, span)))
1763}
1764
1765/// The options `⍠` was given, as `(name, value)` pairs.
1766///
1767/// A bare number is the PRINCIPAL option, which for every function libjay
1768/// gives a variant is the comparison tolerance — so it arrives here named
1769/// `CT`. The other published spelling is one or more parenthesised pairs,
1770/// `⍠('IO' 0)` and `⍠('IO' 0)('CT' 0)`, whose halves are both literals.
1771/// A computed option is a named gap: the variant is settled when the
1772/// program is compiled, as the dialect it overrides is.
1773fn variant_options(
1774    it: &mut std::iter::Peekable<std::vec::IntoIter<Token>>,
1775    span: Span,
1776) -> Result<(Vec<(String, Array)>, Span)> {
1777    if let Some(tok) = it.peek().filter(|t| literal(&t.kind).is_some()) {
1778        let (value, vspan) = (literal(&tok.kind).expect("peeked a literal").clone(), tok.span);
1779        it.next();
1780        return Ok((vec![("CT".to_string(), value)], vspan));
1781    }
1782    let mut options = Vec::new();
1783    let mut last = span;
1784    while it.peek().is_some_and(|t| matches!(t.kind, Tok::LParen)) {
1785        it.next();
1786        let mut inside: Vec<Array> = Vec::new();
1787        loop {
1788            let Some(tok) = it.next() else {
1789                return Err(Error::parse("unmatched ( after ⍠", span));
1790            };
1791            last = tok.span;
1792            if matches!(tok.kind, Tok::RParen) {
1793                break;
1794            }
1795            match literal(&tok.kind) {
1796                Some(a) => inside.push(a.clone()),
1797                None => {
1798                    return Err(Error::not_yet(
1799                        "a computed variant option (f⍠v with a name or an expression)",
1800                        tok.span,
1801                    ));
1802                }
1803            }
1804        }
1805        let [name, value] = inside.as_slice() else {
1806            return Err(Error::parse("a variant option is a name and a value", last));
1807        };
1808        let Data::Char(cs) = &name.data else {
1809            return Err(Error::parse("a variant option starts with its name", last));
1810        };
1811        options.push((cs.as_slice().iter().collect::<String>().to_uppercase(), value.clone()));
1812    }
1813    if options.is_empty() {
1814        let where_ = it.peek().map_or(span, |t| t.span);
1815        return Err(Error::not_yet(
1816            "a computed variant option (f⍠v with a name or an expression)",
1817            where_,
1818        ));
1819    }
1820    Ok((options, last))
1821}
1822
1823/// `f⍠B`: f with the dialect settings B names overridden for this
1824/// application. `CT` is the comparison tolerance, which is the same
1825/// mechanism J spells `!.`; `IO` is the index origin, which is resolved
1826/// into the primitives when the program is compiled, so overriding it
1827/// derives the verb again.
1828fn variant(f: Verb, options: &[(String, Array)], span: Span) -> Result<Verb> {
1829    let mut out = f;
1830    for (name, value) in options {
1831        out = match name.as_str() {
1832            "CT" => {
1833                let Some(ct) = value.to_f64_vec().and_then(|v| v.first().copied()) else {
1834                    return Err(Error::domain("a comparison tolerance is a number", span));
1835                };
1836                if !out.uses_tolerance() {
1837                    return Err(Error::domain(
1838                        format!(
1839                            "the comparison tolerance is not an option of {}: it consults none",
1840                            out.name()
1841                        ),
1842                        span,
1843                    ));
1844                }
1845                if !(0.0..1.0).contains(&ct) {
1846                    return Err(Error::domain(
1847                        "a comparison tolerance lies between 0 and 1",
1848                        span,
1849                    ));
1850                }
1851                Verb::Fit(Box::new(out), ct)
1852            }
1853            "IO" => {
1854                let Some(io) = value.to_i64_vec().and_then(|v| v.first().copied()) else {
1855                    return Err(Error::domain("an index origin is a whole number", span));
1856                };
1857                if io != 0 && io != 1 {
1858                    return Err(Error::domain("an index origin is 0 or 1", span));
1859                }
1860                crate::verb::with_origin(&out, io).ok_or_else(|| {
1861                    Error::domain(
1862                        format!("the index origin is not an option of {}", out.name()),
1863                        span,
1864                    )
1865                })?
1866            }
1867            other => {
1868                return Err(Error::not_yet(
1869                    format!("the variant option {other} (f⍠v)"),
1870                    span,
1871                ));
1872            }
1873        };
1874    }
1875    Ok(out)
1876}
1877
1878/// `(/)` is `/`: parentheses around a bare operator glyph are transparent,
1879/// so what decides the glyph's reading is the token outside them. The pair
1880/// has no other meaning — an operator has no operand inside them — and the
1881/// reference reads `1 0 1(/)1 2 3` as the replication it spells without.
1882fn unwrap_lone_operators(toks: Vec<Token>) -> Vec<Token> {
1883    let mut out: Vec<Token> = Vec::with_capacity(toks.len());
1884    for t in toks {
1885        let n = out.len();
1886        if matches!(t.kind, Tok::RParen)
1887            && n >= 2
1888            && matches!(out[n - 1].kind, Tok::Op(_))
1889            && matches!(out[n - 2].kind, Tok::LParen)
1890        {
1891            let op = out.pop().expect("checked above");
1892            let open = out.pop().expect("checked above");
1893            out.push(Token { kind: op.kind, span: Span::merge(open.span, t.span) });
1894            continue;
1895        }
1896        out.push(t);
1897    }
1898    out
1899}
1900
1901/// A `)` has just been pushed: collapse the pair it closes when what it
1902/// holds is a function. `(f)` is `f` — a function alone in parentheses is
1903/// only grouped — and a run of two or more is a train.
1904fn close_paren(out: &mut Vec<Token>, d: Rules) -> Result<()> {
1905    let close = out.len() - 1;
1906    let Some(open) = matching_lparen(out, close) else { return Ok(()) };
1907    let span = Span::merge(out[open].span, out[close].span);
1908    let inner = &out[open + 1..close];
1909    if inner.len() == 1 && matches!(inner[0].kind, Tok::Func(_)) {
1910        let Some(Token { kind, .. }) = out.get(open + 1).cloned() else {
1911            unreachable!("checked above")
1912        };
1913        out.truncate(open);
1914        out.push(Token { kind, span });
1915        return Ok(());
1916    }
1917    if !d.trains || inner.len() < 2 || !inner[1..].iter().all(|t| matches!(t.kind, Tok::Func(_))) {
1918        return Ok(());
1919    }
1920    let Some(verb) = train(inner)? else { return Ok(()) };
1921    out.truncate(open);
1922    out.push(Token { kind: Tok::Func(verb), span });
1923    Ok(())
1924}
1925
1926/// The `(` that `out[close]` closes, counting the pairs between.
1927fn matching_lparen(out: &[Token], close: usize) -> Option<usize> {
1928    let mut depth = 0usize;
1929    for i in (0..close).rev() {
1930        match out[i].kind {
1931            Tok::RParen => depth += 1,
1932            Tok::LParen => {
1933                if depth == 0 {
1934                    return Some(i);
1935                }
1936                depth -= 1;
1937            }
1938            _ => {}
1939        }
1940    }
1941    None
1942}
1943
1944/// The function a run of tines derives, grouping from the right: a pair is
1945/// an atop `g (h ⍵)`, a triple a fork `(f ⍵) g (h ⍵)`, and a longer run is
1946/// one of those over the train the rest of it makes. The leftmost tine may
1947/// be a value, which stands where `f ⍵` would.
1948///
1949/// `None` where the run is not a train after all, so that the sentence gets
1950/// the reading it would have had; every other refusal is an error, because
1951/// nothing else can be meant by a run of functions.
1952fn train(tines: &[Token]) -> Result<Option<Verb>> {
1953    debug_assert!(!tines.is_empty());
1954    if tines.len() == 1 {
1955        return Ok(match &tines[0].kind {
1956            Tok::Func(f) => Some(f.clone()),
1957            _ => None,
1958        });
1959    }
1960    if tines.len() == 2 {
1961        let (Tok::Func(g), Tok::Func(h)) = (&tines[0].kind, &tines[1].kind) else {
1962            return Ok(None);
1963        };
1964        return Ok(Some(Verb::Atop(Box::new(g.clone()), Box::new(h.clone()))));
1965    }
1966    // An odd run forks its first two tines over the rest; an even one has
1967    // no tine to fork with, so the first is an atop over the rest.
1968    let head = &tines[0].kind;
1969    if tines.len() % 2 == 0 {
1970        let Tok::Func(f) = head else {
1971            return Err(Error::parse(
1972                "a value may only be a fork's left tine, and this train has an even number of tines",
1973                tines[0].span,
1974            ));
1975        };
1976        let Some(rest) = train(&tines[1..])? else { return Ok(None) };
1977        return Ok(Some(Verb::Atop(Box::new(f.clone()), Box::new(rest))));
1978    }
1979    let Some(rest) = train(&tines[2..])? else { return Ok(None) };
1980    let Tok::Func(g) = &tines[1].kind else { unreachable!("the tail is all functions") };
1981    match head {
1982        Tok::Func(f) => {
1983            Ok(Some(Verb::Fork(Box::new(f.clone()), Box::new(g.clone()), Box::new(rest))))
1984        }
1985        Tok::Value(n) | Tok::Nums(n) => {
1986            Ok(Some(Verb::NounFork(n.clone(), Box::new(g.clone()), Box::new(rest))))
1987        }
1988        // A name, an interpolation hole or a bracketed selection is a value
1989        // this frontend only has at run time; a fork's left tine is settled
1990        // when the train is built, as J's is.
1991        Tok::Name(_) | Tok::Param(_) | Tok::RParen | Tok::RBracket | Tok::Niladic(_) => {
1992            Err(Error::not_yet("a train whose left tine is a computed value", tines[0].span))
1993        }
1994        _ => Ok(None),
1995    }
1996}
1997
1998/// The tail of `toks` when it is a run of tines that names a function: the
1999/// value side of `F←+/`, `F←+/÷≢` or `F←2 3⍴⍳`.
2000///
2001/// A single function is that function; two or more are a train. `None`
2002/// where the tail is not a run of tines at all.
2003fn tine_run(toks: &[Token], d: Rules) -> Result<Option<Verb>> {
2004    if !d.trains || toks.is_empty() {
2005        return Ok(None);
2006    }
2007    if !toks[1..].iter().all(|t| matches!(t.kind, Tok::Func(_))) {
2008        return Ok(None);
2009    }
2010    train(toks)
2011}
2012
2013/// `f[k]` where `f` is a plain function rather than a derived one.
2014fn fold_axes(toks: Vec<Token>, d: Rules) -> Result<Vec<Token>> {
2015    let mut out: Vec<Token> = Vec::new();
2016    let mut it = toks.into_iter().peekable();
2017    while let Some(t) = it.next() {
2018        let Tok::Func(f) = &t.kind else {
2019            out.push(t);
2020            continue;
2021        };
2022        let Some((k, aspan)) = take_axis(&mut it, d)? else {
2023            out.push(t);
2024            continue;
2025        };
2026        let Some(inner) = leading_axis_form(f) else {
2027            return Err(Error::not_yet(format!("axis specification for {}", f.name()), aspan));
2028        };
2029        out.push(Token {
2030            kind: Tok::Func(Verb::AlongAxis(Box::new(inner), k)),
2031            span: Span::merge(t.span, aspan),
2032        });
2033    }
2034    Ok(out)
2035}
2036
2037/// The function a glyph means once an axis is named — the leading-axis form
2038/// of the pairs that differ only in which axis they pick. None where libjay
2039/// has no axis form for the function yet.
2040fn leading_axis_form(v: &Verb) -> Option<Verb> {
2041    match v {
2042        // `⌽` is `⊖` applied to rows; with an axis given the two agree.
2043        Verb::Rank(inner, [1, RANK_INF, RANK_INF]) => leading_axis_form(inner),
2044        // `AlongAxis` brings the named axis to the front, so the form under
2045        // it always rotates the LEADING one, whichever glyph was written.
2046        Verb::Prim(p) if matches!(p.monad, MonadOp::Reverse) => {
2047            let mut p = *p;
2048            if matches!(p.dyad, DyadOp::RotateApl { .. }) {
2049                p.dyad = DyadOp::RotateApl { last: false };
2050            }
2051            Some(Verb::Prim(p))
2052        }
2053        _ => None,
2054    }
2055}
2056
2057/// One bracket slot: axis `axis` of the right argument selected by the left.
2058fn select_axis_verb(axis: usize, rank: usize, d: Rules) -> Verb {
2059    Verb::Prim(Prim {
2060        name: "[…]",
2061        monad: MonadOp::None,
2062        dyad: DyadOp::SelectAxis { axis, rank, origin: d.origin },
2063        ranks: [RANK_INF; 3],
2064    })
2065}
2066
2067/// `f⍣n`: one integer atom. APL spells convergence `f⍣≡`, a function right
2068/// operand, which is a separate gap. A NEGATIVE count runs f's inverse
2069/// that many times, and the second answer says so.
2070fn power_spec(a: &Array, span: Span) -> Result<(Power, bool)> {
2071    let ints = a
2072        .to_i64_vec()
2073        .ok_or_else(|| Error::parse("⍣ needs a whole number on its right", span))?;
2074    let [n] = ints[..] else {
2075        return Err(Error::not_yet("power over a list of counts (f⍣n)", span));
2076    };
2077    Ok((Power::Times(n.unsigned_abs()), n < 0))
2078}
2079
2080/// `⍤` rank specification: `n` → [n,n,n]; `a b` → [b,a,b]; `a b c` → [a,b,c].
2081fn rank_spec(a: &Array, span: Span) -> Result<[i64; 3]> {
2082    let ints = a
2083        .to_i64_vec()
2084        .ok_or_else(|| Error::parse("⍤ rank specification must be integers", span))?;
2085    match ints.len() {
2086        1 => Ok([ints[0], ints[0], ints[0]]),
2087        2 => Ok([ints[1], ints[0], ints[1]]),
2088        3 => Ok([ints[0], ints[1], ints[2]]),
2089        _ => Err(Error::parse("⍤ rank specification takes 1 to 3 integers", span)),
2090    }
2091}
2092
2093// ---------------------------------------------------------------------------
2094// Parser
2095// ---------------------------------------------------------------------------
2096
2097/// Parse the token range `[lo, hi)` as one expression, right to left.
2098/// `hint` locates errors when the range is empty.
2099fn parse_range(toks: &[Token], lo: usize, hi: usize, hint: Span, d: Rules) -> Result<Expr> {
2100    let (mut acc, mut start) = parse_operand(toks, lo, hi, hint, d)?;
2101    let end = toks[hi - 1].span.end;
2102    loop {
2103        if start == lo {
2104            return Ok(acc);
2105        }
2106        let left = &toks[start - 1];
2107        match &left.kind {
2108            Tok::Func(f) => {
2109                // Dyadic exactly when an operand ends to the left of `f`.
2110                let dyadic = start >= lo + 2 && is_operand_end(&toks[start - 2].kind);
2111                if dyadic {
2112                    let (x, xstart) = parse_operand(toks, lo, start - 1, left.span, d)?;
2113                    acc = Expr::Dyad {
2114                        verb: f.clone(),
2115                        x: Box::new(x),
2116                        y: Box::new(acc),
2117                        span: Span::new(toks[xstart].span.start, end),
2118                    };
2119                    start = xstart;
2120                } else {
2121                    acc = Expr::Monad {
2122                        verb: f.clone(),
2123                        y: Box::new(acc),
2124                        span: Span::new(left.span.start, end),
2125                    };
2126                    start -= 1;
2127                }
2128            }
2129            Tok::Assign => {
2130                if start < lo + 2 {
2131                    return Err(Error::parse("assignment target must be a name", left.span));
2132                }
2133                let target = &toks[start - 2];
2134                let span = Span::new(target.span.start, end);
2135                match &target.kind {
2136                    Tok::Name(n) => {
2137                        acc = Expr::Assign {
2138                            name: n.clone(),
2139                            value: Box::new(acc),
2140                            scope: Scope::Local,
2141                            span,
2142                        };
2143                    }
2144                    Tok::Quad { quote } => {
2145                        acc = Expr::PrintPass { value: Box::new(acc), bare: *quote, span };
2146                    }
2147                    _ => {
2148                        return Err(Error::parse(
2149                            "assignment target must be a name",
2150                            target.span,
2151                        ));
2152                    }
2153                }
2154                start -= 2;
2155            }
2156            // Adjacent operands are vector notation, which `parse_operand`
2157            // has already taken as one operand by the time we get here.
2158            _ => break,
2159        }
2160    }
2161    let span = Span::new(toks[lo].span.start, toks[start - 1].span.end);
2162    // A run of functions is a train, and a train is a function: standing
2163    // where a value belongs, it is missing its argument. Parenthesised, it
2164    // has already become one function by the time the parser sees it.
2165    if d.trains && toks[lo..start].iter().all(|t| matches!(t.kind, Tok::Func(_))) {
2166        return Err(Error::parse(
2167            "a train is a function; parenthesise it to apply it to an argument",
2168            span,
2169        ));
2170    }
2171    Err(Error::parse("syntax error", span))
2172}
2173
2174/// Parse the operand ending at `hi - 1`, vector notation included.
2175///
2176/// Juxtaposed operands are the items of one vector: every primary
2177/// contributes one item, except a run of numeric literals, whose numbers
2178/// are items of their own — which is why `1 2 (3 4)` has three items and
2179/// `'ab' 'cd'` has two.
2180fn parse_operand(
2181    toks: &[Token],
2182    lo: usize,
2183    hi: usize,
2184    hint: Span,
2185    d: Rules,
2186) -> Result<(Expr, usize)> {
2187    let (first, mut start) = parse_primary(toks, lo, hi, hint, d)?;
2188    if start == lo || !is_operand_end(&toks[start - 1].kind) {
2189        return Ok((first, start));
2190    }
2191    let mut items: Vec<Expr> = Vec::new();
2192    let mut cur = first;
2193    loop {
2194        push_items(&mut items, cur, &toks[start]);
2195        if start == lo || !is_operand_end(&toks[start - 1].kind) {
2196            break;
2197        }
2198        let (e, s) = parse_primary(toks, lo, start, toks[start - 1].span, d)?;
2199        cur = e;
2200        start = s;
2201    }
2202    let span = Span::new(toks[start].span.start, toks[hi - 1].span.end);
2203    let mut it = items.into_iter();
2204    let last = it.next().expect("a strand has at least one item");
2205    let mut acc = Expr::Monad { verb: strand_seed(d), y: Box::new(last), span };
2206    for item in it {
2207        acc = Expr::Dyad { verb: strand_verb(), x: Box::new(item), y: Box::new(acc), span };
2208    }
2209    Ok((acc, start))
2210}
2211
2212/// The items one primary contributes to a strand, appended right to left.
2213fn push_items(items: &mut Vec<Expr>, e: Expr, tok: &Token) {
2214    if let Tok::Nums(a) = &tok.kind && a.rank() > 0 {
2215        for i in (0..a.count()).rev() {
2216            let atom = Array::new(Vec::new(), a.data.slice(i, i + 1));
2217            items.push(Expr::Const(atom, tok.span));
2218        }
2219        return;
2220    }
2221    items.push(e);
2222}
2223
2224/// `,⊂y`: the one-item vector a single operand makes — flat when the
2225/// operand is a simple scalar, nested when it is anything else.
2226fn strand_seed(d: Rules) -> Verb {
2227    Verb::Atop(
2228        Box::new(Verb::Prim(prim_for(',', d).expect("`,` is a primitive"))),
2229        Box::new(Verb::Prim(prim_for('⊂', d).expect("`⊂` is a primitive"))),
2230    )
2231}
2232
2233/// `x` prepended to the strand `y` as one more item.
2234fn strand_verb() -> Verb {
2235    Verb::Prim(Prim {
2236        name: "(vector notation)",
2237        monad: MonadOp::None,
2238        dyad: DyadOp::Strand,
2239        ranks: [RANK_INF; 3],
2240    })
2241}
2242
2243/// Parse the single operand ending at `hi - 1`. Returns the expression and
2244/// the index of its first token.
2245fn parse_primary(
2246    toks: &[Token],
2247    lo: usize,
2248    hi: usize,
2249    hint: Span,
2250    d: Rules,
2251) -> Result<(Expr, usize)> {
2252    if hi == lo {
2253        return Err(Error::parse("empty parentheses", hint));
2254    }
2255    let t = &toks[hi - 1];
2256    match &t.kind {
2257        Tok::Value(a) | Tok::Nums(a) => Ok((Expr::Const(a.clone(), t.span), hi - 1)),
2258        Tok::Param(i) => Ok((Expr::Param(*i, t.span), hi - 1)),
2259        Tok::Name(n) => Ok((Expr::Name(n.clone(), t.span), hi - 1)),
2260        // Naming a niladic definition runs it; the argument it is handed
2261        // is the empty one its body cannot reach.
2262        Tok::Niladic(v) => Ok((
2263            Expr::Monad {
2264                verb: v.clone(),
2265                y: Box::new(Expr::Const(Array::empty(crate::dtype::DType::I64), t.span)),
2266                span: t.span,
2267            },
2268            hi - 1,
2269        )),
2270        Tok::RParen => {
2271            let l = match_lparen(toks, lo, hi - 1)?;
2272            let hint = Span::merge(toks[l].span, t.span);
2273            let inner = parse_range(toks, l + 1, hi - 1, hint, d)?;
2274            Ok((inner, l))
2275        }
2276        Tok::RBracket => index_brackets(toks, lo, hi, d),
2277        // `F←+/` names a function. A whole sentence that does so is settled
2278        // in `parse_statement`; reaching here means the assignment is
2279        // nested inside a larger sentence, which names nothing.
2280        Tok::Func(_) if hi >= lo + 2 && matches!(toks[hi - 2].kind, Tok::Assign) => {
2281            let from = if hi >= lo + 3 { toks[hi - 3].span } else { toks[hi - 2].span };
2282            let span = Span::merge(from, t.span);
2283            if d.trains {
2284                Err(Error::not_yet("naming a function inside a larger sentence", span))
2285            } else {
2286                Err(Error::not_yet("function assignment (F←+/)", span))
2287            }
2288        }
2289        Tok::Func(_) => Err(Error::parse("missing right argument", t.span)),
2290        Tok::Assign => Err(Error::parse("← needs a value on its right", t.span)),
2291        // `⍞` is the line itself, `⎕` the value the line evaluates to.
2292        Tok::Quad { quote } => Ok((Expr::Input { eval: !*quote, span: t.span }, hi - 1)),
2293        Tok::LParen => Err(Error::parse("unmatched (", t.span)),
2294        Tok::LBracket => Err(Error::parse("unmatched [", t.span)),
2295        Tok::Semi => Err(Error::parse("; is only meaningful inside index brackets", t.span)),
2296        Tok::Colon => Err(Error::parse(": is only meaningful in a dfn guard", t.span)),
2297        Tok::UserOp { .. } => Err(Error::parse(
2298            "this dfn mentions ⍺⍺ or ⍵⍵, so it is an operator and needs a function operand",
2299            t.span,
2300        )),
2301        Tok::Arrow => Err(Error::parse(
2302            "→ branches, and only a line of a ∇ definition may begin with it",
2303            t.span,
2304        )),
2305        Tok::Del => Err(Error::parse("∇ opens a definition; it is not a value", t.span)),
2306        Tok::Control(w) => Err(Error::parse(
2307            format!(":{w} is only meaningful inside a ∇ definition"),
2308            t.span,
2309        )),
2310        Tok::LBrace | Tok::RBrace => Err(Error::parse("unmatched {", t.span)),
2311        Tok::Separator => Err(Error::internal("a statement break survived folding")),
2312        Tok::Op(_) => Err(Error::internal("operator survived folding")),
2313        // The sentence parser never sees a `⎕FX` that libjay could fix:
2314        // one is rewritten away before the sentence is parsed. What is
2315        // left is a `⎕FX` inside another definition, whose body is not
2316        // compiled until it is called.
2317        Tok::QuadFx => Err(Error::not_yet("⎕FX inside another definition", t.span)),
2318    }
2319}
2320
2321/// `A[i;j]`: one slot per axis, an empty slot meaning the whole axis.
2322///
2323/// The slots are applied from the last axis to the first, so a scalar slot
2324/// dropping its axis leaves the axes still to come where they were. The
2325/// slot that sees the whole array — the last one applied — carries the
2326/// check that there is one slot per axis.
2327fn index_brackets(
2328    toks: &[Token],
2329    lo: usize,
2330    hi: usize,
2331    d: Rules,
2332) -> Result<(Expr, usize)> {
2333    let close = &toks[hi - 1];
2334    let open = match_lbracket(toks, lo, hi - 1)?;
2335    if open == lo || !is_operand_end(&toks[open - 1].kind) {
2336        return Err(Error::parse("[ needs a value on its left", toks[open].span));
2337    }
2338    let (base, start) = parse_primary(toks, lo, open, toks[open].span, d)?;
2339    let slots = index_slots(toks, open + 1, hi - 1, toks[open].span)?;
2340    let span = Span::new(toks[start].span.start, close.span.end);
2341    let rank = slots.len();
2342    let mut acc = base;
2343    let mut first = true;
2344    for (axis, slot) in slots.iter().enumerate().rev() {
2345        let Some((slo, shi)) = *slot else { continue };
2346        let idx = parse_range(toks, slo, shi, toks[open].span, d)?;
2347        let check = if first { rank } else { 0 };
2348        first = false;
2349        acc = Expr::Dyad {
2350            verb: select_axis_verb(axis, check, d),
2351            x: Box::new(idx),
2352            y: Box::new(acc),
2353            span,
2354        };
2355    }
2356    Ok((acc, start))
2357}
2358
2359/// The token ranges of the slots between `[` and `]`, in axis order. None
2360/// is an elided slot, which selects the whole axis.
2361fn index_slots(
2362    toks: &[Token],
2363    lo: usize,
2364    hi: usize,
2365    hint: Span,
2366) -> Result<Vec<Option<(usize, usize)>>> {
2367    let mut out = Vec::new();
2368    let mut depth = 0usize;
2369    let mut start = lo;
2370    for (i, t) in toks.iter().enumerate().take(hi).skip(lo) {
2371        match t.kind {
2372            Tok::LParen | Tok::LBracket => depth += 1,
2373            Tok::RParen | Tok::RBracket => depth -= 1,
2374            Tok::Semi if depth == 0 => {
2375                out.push((start < i).then_some((start, i)));
2376                start = i + 1;
2377            }
2378            _ => {}
2379        }
2380    }
2381    out.push((start < hi).then_some((start, hi)));
2382    if out.len() == 1 && out[0].is_none() {
2383        return Err(Error::parse("empty index brackets", hint));
2384    }
2385    Ok(out)
2386}
2387
2388fn match_lbracket(toks: &[Token], lo: usize, rbracket: usize) -> Result<usize> {
2389    let mut depth = 0usize;
2390    let mut i = rbracket;
2391    while i > lo {
2392        i -= 1;
2393        match toks[i].kind {
2394            Tok::RBracket => depth += 1,
2395            Tok::LBracket => {
2396                if depth == 0 {
2397                    return Ok(i);
2398                }
2399                depth -= 1;
2400            }
2401            _ => {}
2402        }
2403    }
2404    Err(Error::parse("unmatched ]", toks[rbracket].span))
2405}
2406
2407fn match_lparen(toks: &[Token], lo: usize, rparen: usize) -> Result<usize> {
2408    let mut depth = 0usize;
2409    let mut i = rparen;
2410    while i > lo {
2411        i -= 1;
2412        match toks[i].kind {
2413            Tok::RParen => depth += 1,
2414            Tok::LParen => {
2415                if depth == 0 {
2416                    return Ok(i);
2417                }
2418                depth -= 1;
2419            }
2420            _ => {}
2421        }
2422    }
2423    Err(Error::parse("unmatched )", toks[rparen].span))
2424}
2425
2426// ---------------------------------------------------------------------------
2427// Tests
2428// ---------------------------------------------------------------------------
2429
2430// ---------------------------------------------------------------------------
2431// Explicit definitions
2432// ---------------------------------------------------------------------------
2433
2434/// APL's control words, without their colon. `:End` closes any of the
2435/// structures, which is the spelling GNU APL's manual gives as an
2436/// alternative to the named closers.
2437const CONTROL_WORDS: [&str; 18] = [
2438    "If", "ElseIf", "Else", "EndIf", "While", "EndWhile", "Repeat", "Until", "For", "In",
2439    "EndFor", "Select", "Case", "EndSelect", "Return", "Leave", "Continue", "End",
2440];
2441
2442/// The control word a `:name` spells, case-insensitively as the references
2443/// accept it.
2444fn control_word(word: &str) -> Option<&'static str> {
2445    CONTROL_WORDS.iter().copied().find(|w| w.eq_ignore_ascii_case(word))
2446}
2447
2448/// The index of the token matching the bracket that opens at `open`.
2449fn match_close(toks: &[Token], open: usize, opener: &Tok, closer: &Tok) -> Option<usize> {
2450    let same = |a: &Tok, b: &Tok| std::mem::discriminant(a) == std::mem::discriminant(b);
2451    let mut depth = 0usize;
2452    for (i, t) in toks.iter().enumerate().skip(open) {
2453        if same(&t.kind, opener) {
2454            depth += 1;
2455        } else if same(&t.kind, closer) {
2456            depth -= 1;
2457            if depth == 0 {
2458                return Some(i);
2459            }
2460        }
2461    }
2462    None
2463}
2464
2465/// Replace every `{ … }` in a sentence by the function it defines.
2466fn fold_dfns(
2467    toks: Vec<Token>,
2468    d: Rules,
2469    verbs: &HashMap<String, Verb>,
2470) -> Result<Vec<Token>> {
2471    let Some(open) = toks.iter().position(|t| matches!(t.kind, Tok::LBrace)) else {
2472        return Ok(toks);
2473    };
2474    let close = match_close(&toks, open, &Tok::LBrace, &Tok::RBrace)
2475        .ok_or_else(|| Error::parse("unmatched {", toks[open].span))?;
2476    let span = Span::merge(toks[open].span, toks[close].span);
2477    let mut out: Vec<Token> = toks[..open].to_vec();
2478    let kind = match build_dfn(&toks[open + 1..close], d, verbs)? {
2479        Dfn::Func(verb) => Tok::Func(*verb),
2480        Dfn::Op { def, omega } => Tok::UserOp { def, omega },
2481    };
2482    out.push(Token { kind, span });
2483    out.extend_from_slice(&toks[close + 1..]);
2484    // A sentence may hold several dfns side by side.
2485    fold_dfns(out, d, verbs)
2486}
2487
2488/// The statements of a dfn body: the runs between the `⋄` and line breaks
2489/// that belong to this dfn rather than to one nested inside it.
2490fn split_statements(toks: &[Token]) -> Vec<&[Token]> {
2491    let mut out = Vec::new();
2492    let mut depth = 0usize;
2493    let mut start = 0usize;
2494    for (i, t) in toks.iter().enumerate() {
2495        match t.kind {
2496            Tok::LBrace => depth += 1,
2497            Tok::RBrace => depth = depth.saturating_sub(1),
2498            Tok::Separator if depth == 0 => {
2499                out.push(&toks[start..i]);
2500                start = i + 1;
2501            }
2502            _ => {}
2503        }
2504    }
2505    out.push(&toks[start..]);
2506    out.into_iter().filter(|s| !s.is_empty()).collect()
2507}
2508
2509/// What a `{ … }` defines: a plain function, or an operator still waiting
2510/// for its operands.
2511enum Dfn {
2512    Func(Box<Verb>),
2513    Op { def: Arc<OpDef>, omega: bool },
2514}
2515
2516// The dfns being built on this thread, outermost first, and the counter
2517// that hands each one its identity.
2518//
2519// A dfn's body is parsed by a nested call, so this chain is exactly the
2520// run of `{` the parser is inside. It lives here rather than in a
2521// parameter because every step between one `build_dfn` and the next — the
2522// statement splitter, the guard reader, the sentence parser — would
2523// otherwise carry it through untouched.
2524thread_local! {
2525    static ENCLOSING: std::cell::RefCell<Vec<u64>> =
2526        const { std::cell::RefCell::new(Vec::new()) };
2527    static NEXT_DFN_ID: std::cell::Cell<u64> = const { std::cell::Cell::new(1) };
2528}
2529
2530/// `{ … }`: the body's own words decide the valence, and `∇` in it names
2531/// the dfn itself.
2532/// The verb a dfn defines, and — when it mentions `⍺⍺` or `⍵⍵` — whether
2533/// it is an operator wanting a right operand as well as a left one.
2534fn build_dfn(body: &[Token], d: Rules, verbs: &HashMap<String, Verb>) -> Result<Dfn> {
2535    let mut depth = 0usize;
2536    let mut dyadic = false;
2537    let mut alpha_op = false;
2538    let mut omega_op = false;
2539    for t in body {
2540        match &t.kind {
2541            Tok::LBrace => depth += 1,
2542            Tok::RBrace => depth = depth.saturating_sub(1),
2543            Tok::Name(n) if depth == 0 && n == "⍺" => dyadic = true,
2544            Tok::Name(n) if depth == 0 && n == "⍺⍺" => alpha_op = true,
2545            Tok::Name(n) if depth == 0 && n == "⍵⍵" => omega_op = true,
2546            _ => {}
2547        }
2548    }
2549    // An operand name is a FUNCTION inside the body unless this reading
2550    // has an array standing there, in which case it is an ordinary name
2551    // and the body's `⍺⍺+⍵` is a sum rather than a train.
2552    let reading = |alpha_value: bool, omega_value: bool| -> Result<Verb> {
2553        let mut inner = verbs.clone();
2554        if alpha_op && !alpha_value {
2555            inner.insert("⍺⍺".to_string(), Verb::Named("⍺⍺".to_string()));
2556        }
2557        if (alpha_op || omega_op) && !omega_value {
2558            inner.insert("⍵⍵".to_string(), Verb::Named("⍵⍵".to_string()));
2559        }
2560        // The body is parsed with this dfn on the enclosing chain, so that
2561        // a dfn written inside it records this one as a lexical parent.
2562        let id = NEXT_DFN_ID.with(|c| {
2563            let id = c.get();
2564            c.set(id.wrapping_add(1));
2565            id
2566        });
2567        let enclosing = ENCLOSING.with(|e| e.borrow().clone());
2568        ENCLOSING.with(|e| e.borrow_mut().push(id));
2569        let parsed = parse_dfn_body(body, d, &mut inner);
2570        ENCLOSING.with(|e| {
2571            e.borrow_mut().pop();
2572        });
2573        let mut stmts = parsed?;
2574        // The body is a sequence, and the dialect says which of its
2575        // sentences is the answer. libjay's block model gives the last
2576        // one; the other reading stops at the first sentence that is not
2577        // an assignment.
2578        match d.dfn_result {
2579            DfnResult::LastSentence => {}
2580            DfnResult::FirstNonAssignment => {
2581                // A guard is a control structure that returns when it
2582                // holds, so it is not the sentence looked for; the
2583                // sentences after the one that is are never run.
2584                let plain = |e: &Expr| {
2585                    !matches!(
2586                        e,
2587                        Expr::Assign { .. }
2588                            | Expr::AmendIndex { .. }
2589                            | Expr::Control(..)
2590                            | Expr::VerbDef { .. }
2591                            | Expr::ModDef { .. }
2592                    )
2593                };
2594                if let Some(k) = stmts.iter().position(plain) {
2595                    stmts.truncate(k + 1);
2596                }
2597            }
2598        }
2599        let pure = stmts.iter().all(is_pure_stmt);
2600        Ok(Verb::Explicit(Arc::new(ExplicitDef {
2601            name: "{…}".to_string(),
2602            left: dyadic.then(|| "⍺".to_string()),
2603            right: "⍵".to_string(),
2604            // A dfn runs in either valence; a monadic call simply leaves
2605            // `⍺` without a value, unless `⍺←` gives it one, and a left
2606            // argument it has no name for is dropped rather than refused.
2607            dyad_only: false,
2608            spare_left: true,
2609            result: None,
2610            locals: Vec::new(),
2611            body: stmts,
2612            // A dfn that reaches its end without a value has no result.
2613            empty: None,
2614            labels: Vec::new(),
2615            enclosing,
2616            id,
2617            pure,
2618        })))
2619    };
2620    if !(alpha_op || omega_op) {
2621        return Ok(Dfn::Func(Box::new(reading(false, false)?)));
2622    }
2623    // Every reading is parsed here, and the operands choose one when they
2624    // arrive. A reading that does not parse keeps its diagnostic instead of
2625    // failing the definition: the body only has to make sense under the
2626    // operands it is actually given.
2627    let mut readings: [std::result::Result<Verb, String>; 4] =
2628        [const { Err(String::new()) }; 4];
2629    for (i, slot) in readings.iter_mut().enumerate() {
2630        *slot = reading(i & 1 != 0, i & 2 != 0).map_err(|e| e.msg);
2631    }
2632    // The reading with function operands throughout is the one every
2633    // existing spelling uses; if it will not parse, nothing will.
2634    if let Err(msg) = &readings[0]
2635        && readings.iter().all(|r| r.is_err())
2636    {
2637        return Err(Error::parse(msg.clone(), body.first().map_or(Span::new(0, 0), |t| t.span)));
2638    }
2639    Ok(Dfn::Op { def: Arc::new(OpDef { readings }), omega: omega_op })
2640}
2641
2642fn parse_dfn_body(
2643    body: &[Token],
2644    d: Rules,
2645    verbs: &mut HashMap<String, Verb>,
2646) -> Result<Vec<Expr>> {
2647    let mut stmts = Vec::new();
2648    for stmt in split_statements(body) {
2649        // `∇` inside a dfn is the dfn itself.
2650        let stmt: Vec<Token> = stmt
2651            .iter()
2652            .map(|t| match t.kind {
2653                Tok::Del => Token { kind: Tok::Func(Verb::SelfRef), span: t.span },
2654                _ => t.clone(),
2655            })
2656            .collect();
2657        stmts.push(parse_guarded(stmt, d, verbs)?);
2658    }
2659    Ok(stmts)
2660}
2661
2662/// One dfn statement: a guard `cond:expr`, an `⍺←default`, or a sentence.
2663fn parse_guarded(
2664    stmt: Vec<Token>,
2665    d: Rules,
2666    verbs: &mut HashMap<String, Verb>,
2667) -> Result<Expr> {
2668    let mut depth = 0usize;
2669    let mut colon = None;
2670    for (i, t) in stmt.iter().enumerate() {
2671        match t.kind {
2672            Tok::LBrace | Tok::LParen | Tok::LBracket => depth += 1,
2673            Tok::RBrace | Tok::RParen | Tok::RBracket => depth = depth.saturating_sub(1),
2674            Tok::Colon if depth == 0 => {
2675                colon = Some(i);
2676                break;
2677            }
2678            _ => {}
2679        }
2680    }
2681    if let Some(k) = colon {
2682        let span = Span::merge(stmt[0].span, stmt[stmt.len() - 1].span);
2683        let test = one_statement(stmt[..k].to_vec(), d, verbs, stmt[k].span)?;
2684        let body = one_statement(stmt[k + 1..].to_vec(), d, verbs, stmt[k].span)?;
2685        // A guard that holds is the dfn's answer: the value, then out.
2686        return Ok(Expr::Control(
2687            Box::new(Control::Guard {
2688                test: vec![test],
2689                body: vec![body, Expr::Control(Box::new(Control::Return), span)],
2690            }),
2691            span,
2692        ));
2693    }
2694    // `⍺←v` gives the left argument a value only where none arrived. The
2695    // dialect says whether `v` is evaluated when one did: eagerly, the
2696    // sentence runs and its value is dropped.
2697    let default = matches!(
2698        (stmt.first().map(|t| &t.kind), stmt.get(1).map(|t| &t.kind)),
2699        (Some(Tok::Name(n)), Some(Tok::Assign)) if n == "⍺"
2700    );
2701    let span = stmt.first().map_or(Span::new(0, 0), |t| t.span);
2702    let e = one_statement(stmt, d, verbs, span)?;
2703    if default {
2704        let scope = match d.default_arg {
2705            DefaultArg::Eager => Scope::LocalDefault,
2706            DefaultArg::Lazy => return Err(Error::not_yet("a lazy ⍺← default", span)),
2707        };
2708        if let Expr::Assign { name, value, span, .. } = e {
2709            return Ok(Expr::Assign { name, value, scope, span });
2710        }
2711    }
2712    Ok(e)
2713}
2714
2715fn one_statement(
2716    stmt: Vec<Token>,
2717    d: Rules,
2718    verbs: &mut HashMap<String, Verb>,
2719    hint: Span,
2720) -> Result<Expr> {
2721    parse_statement(stmt, d, verbs, false)?
2722        .ok_or_else(|| Error::parse("this needs an expression", hint))
2723}
2724
2725/// True when nothing in this sentence can have an effect beyond its value.
2726fn is_pure_stmt(e: &Expr) -> bool {
2727    match e {
2728        Expr::Const(..) | Expr::Param(..) | Expr::Name(..) => true,
2729        Expr::Monad { verb, y, .. } => verb.is_pure() && is_pure_stmt(y),
2730        Expr::Dyad { verb, x, y, .. } => verb.is_pure() && is_pure_stmt(x) && is_pure_stmt(y),
2731        Expr::Assign { value, .. } => is_pure_stmt(value),
2732        Expr::Control(c, _) => is_pure_control(c),
2733        _ => false,
2734    }
2735}
2736
2737fn is_pure_control(c: &Control) -> bool {
2738    let all = |b: &Vec<Expr>| b.iter().all(is_pure_stmt);
2739    match c {
2740        Control::Return | Control::Break | Control::Continue => true,
2741        Control::Branch(target) => is_pure_stmt(target),
2742        Control::If { arms, otherwise } => {
2743            arms.iter().all(|a| a.test.as_ref().is_none_or(all) && all(&a.body))
2744                && otherwise.as_ref().is_none_or(all)
2745        }
2746        Control::While { test, body, .. } => all(test) && all(body),
2747        Control::For { source, body, .. } => is_pure_stmt(source) && all(body),
2748        Control::Select { subject, cases } => {
2749            is_pure_stmt(subject)
2750                && cases.iter().all(|c| c.test.as_ref().is_none_or(all) && all(&c.body))
2751        }
2752        Control::Try { body, catch } => all(body) && all(catch),
2753        Control::Guard { test, body } => all(test) && all(body),
2754    }
2755}
2756
2757// ---------------------------------------------------------------------------
2758// ∇-definitions and control structures
2759// ---------------------------------------------------------------------------
2760
2761/// `∇ Z←L F R;a;b` … `∇`: the multi-line definition form, which is where
2762/// APL puts its control structures.
2763fn parse_tradfn(
2764    sentences: &[Vec<Token>],
2765    i: &mut usize,
2766    d: Rules,
2767    verbs: &mut HashMap<String, Verb>,
2768) -> Result<Expr> {
2769    let header = &sentences[*i];
2770    let open = header[0].span;
2771    *i += 1;
2772    let head = header[1..].to_vec();
2773    let mut body_lines: Vec<Vec<Token>> = Vec::new();
2774    loop {
2775        let Some(line) = sentences.get(*i) else {
2776            return Err(Error::parse("this definition has no closing ∇", open));
2777        };
2778        *i += 1;
2779        if line.len() == 1 && matches!(line[0].kind, Tok::Del) {
2780            break;
2781        }
2782        body_lines.push(line.clone());
2783    }
2784    let close = sentences
2785        .get(i.saturating_sub(1))
2786        .and_then(|l| l.first())
2787        .map_or(open, |t| t.span);
2788    build_tradfn(&head, &body_lines, Span::merge(open, close), d, verbs)
2789}
2790
2791/// A definition from its header tokens and one token list per body line —
2792/// the shape both `∇ … ∇` and `⎕FX` reduce to.
2793fn build_tradfn(
2794    head: &[Token],
2795    body_lines: &[Vec<Token>],
2796    span: Span,
2797    d: Rules,
2798    verbs: &mut HashMap<String, Verb>,
2799) -> Result<Expr> {
2800    let (name, def_left, def_right, result, locals) = parse_header(head, span)?;
2801    // The body can call the function by its own name.
2802    let mut inner = verbs.clone();
2803    inner.insert(name.clone(), Verb::Named(name.clone()));
2804    let mut items = Vec::new();
2805    let mut labels: Vec<(String, usize)> = Vec::new();
2806    for line in body_lines {
2807        let mut label = None;
2808        let item = to_item(line.clone(), d, &mut inner, &mut label)?;
2809        if let Some(name) = label {
2810            labels.push((name, items.len()));
2811        }
2812        items.push(item);
2813    }
2814    let item_count = items.len();
2815    let mut cursor = AplCursor { items: &items, at: 0, d };
2816    let mut body = parse_apl_block(&mut cursor, &[])?;
2817    // A label is the number of a LINE, so the statements have to be the
2818    // lines: a control structure folds several of them into one and the
2819    // numbering would no longer mean anything.
2820    if !labels.is_empty() && body.len() != item_count {
2821        return Err(Error::not_yet("a label and a control structure in one definition", span));
2822    }
2823    if let Some(item) = cursor.peek() {
2824        return Err(Error::parse(
2825            format!(":{} has no matching opening word", item.word().unwrap_or("?")),
2826            item.span(),
2827        ));
2828    }
2829    // A `∇` definition's names are global unless the header declares them:
2830    // that is APL's rule, and the reference holds to it.
2831    let mut own: Vec<String> = locals.clone();
2832    own.extend(result.clone());
2833    own.extend(def_left.clone());
2834    own.push(def_right.clone());
2835    for stmt in &mut body {
2836        set_scopes(stmt, &own);
2837    }
2838    let pure = body.iter().all(is_pure_stmt);
2839    let verb = Verb::Explicit(Arc::new(ExplicitDef {
2840        name: format!("∇{name}"),
2841        left: def_left,
2842        right: def_right,
2843        dyad_only: false,
2844        // A `∇` definition binds its arguments by the names its header
2845        // gives, and refuses one it has no name for. Only a dfn nests
2846        // lexically inside another.
2847        spare_left: false,
2848        enclosing: Vec::new(),
2849        id: 0,
2850        result,
2851        locals,
2852        body,
2853        empty: None,
2854        labels,
2855        pure,
2856    }));
2857    verbs.insert(name.clone(), verb.clone());
2858    Ok(Expr::VerbDef { name, verb, span })
2859}
2860
2861type Header = (String, Option<String>, String, Option<String>, Vec<String>);
2862
2863/// `Z←L F R;a;b` and its shorter forms.
2864fn parse_header(toks: &[Token], span: Span) -> Result<Header> {
2865    let mut names: Vec<String> = Vec::new();
2866    let mut locals: Vec<String> = Vec::new();
2867    let mut result = None;
2868    let mut in_locals = false;
2869    let mut k = 0usize;
2870    // `Z←` in front names the result.
2871    if let (Some(Tok::Name(z)), Some(Tok::Assign)) =
2872        (toks.first().map(|t| &t.kind), toks.get(1).map(|t| &t.kind))
2873    {
2874        result = Some(z.clone());
2875        k = 2;
2876    }
2877    while k < toks.len() {
2878        match &toks[k].kind {
2879            Tok::Semi => in_locals = true,
2880            Tok::Name(n) if in_locals => locals.push(n.clone()),
2881            Tok::Name(n) => names.push(n.clone()),
2882            _ => {
2883                return Err(Error::parse("this is not a ∇ definition header", toks[k].span));
2884            }
2885        }
2886        k += 1;
2887    }
2888    match names.len() {
2889        3 => Ok((names[1].clone(), Some(names[0].clone()), names[2].clone(), result, locals)),
2890        2 => Ok((names[0].clone(), None, names[1].clone(), result, locals)),
2891        1 => Ok((names[0].clone(), None, crate::ir::NILADIC.to_string(), result, locals)),
2892        _ => Err(Error::parse("a ∇ definition header names a function and its arguments", span)),
2893    }
2894}
2895
2896/// One line of a `∇` definition: a control word with what follows it, or a
2897/// sentence already lowered.
2898enum AplItem {
2899    Sentence(Expr),
2900    Word { word: &'static str, rest: Vec<Token>, span: Span },
2901}
2902
2903impl AplItem {
2904    fn word(&self) -> Option<&'static str> {
2905        match self {
2906            AplItem::Word { word, .. } => Some(word),
2907            AplItem::Sentence(_) => None,
2908        }
2909    }
2910
2911    fn span(&self) -> Span {
2912        match self {
2913            AplItem::Word { span, .. } => *span,
2914            AplItem::Sentence(e) => e.span(),
2915        }
2916    }
2917}
2918
2919fn to_item(
2920    line: Vec<Token>,
2921    d: Rules,
2922    verbs: &mut HashMap<String, Verb>,
2923    label: &mut Option<String>,
2924) -> Result<AplItem> {
2925    let mut line = line;
2926    // `L:` in front of a line names it, and `→` takes the number of the
2927    // line a name stands for.
2928    if let (Some(Tok::Name(n)), Some(Tok::Colon)) =
2929        (line.first().map(|t| &t.kind), line.get(1).map(|t| &t.kind))
2930    {
2931        *label = Some(n.clone());
2932        line.drain(..2);
2933    }
2934    if let Some(Tok::Control(word)) = line.first().map(|t| &t.kind) {
2935        let word = *word;
2936        let span = line[0].span;
2937        return Ok(AplItem::Word { word, rest: line[1..].to_vec(), span });
2938    }
2939    if matches!(line.first().map(|t| &t.kind), Some(Tok::Arrow)) {
2940        let span = line[0].span;
2941        let target = parse_statement(line[1..].to_vec(), d, verbs, true)?
2942            .ok_or_else(|| Error::parse("→ needs a line to branch to", span))?;
2943        let span = Span::merge(span, target.span());
2944        return Ok(AplItem::Sentence(Expr::Control(
2945            Box::new(Control::Branch(Box::new(target))),
2946            span,
2947        )));
2948    }
2949    // A labelled line may hold nothing else; the label is then a place to
2950    // branch to and the line does no work of its own. `→⍬` is exactly that
2951    // line: a branch with no target falls through and yields nothing.
2952    if line.is_empty() {
2953        let span = label.as_ref().map_or(Span::new(0, 0), |_| Span::new(0, 0));
2954        let nowhere = Expr::Const(Array::empty(crate::dtype::DType::I64), span);
2955        return Ok(AplItem::Sentence(Expr::Control(
2956            Box::new(Control::Branch(Box::new(nowhere))),
2957            span,
2958        )));
2959    }
2960    let span = line.first().map_or(Span::new(0, 0), |t| t.span);
2961    let e = parse_statement(line, d, verbs, true)?
2962        .ok_or_else(|| Error::parse("this line has no sentence", span))?;
2963    Ok(AplItem::Sentence(e))
2964}
2965
2966struct AplCursor<'a> {
2967    items: &'a [AplItem],
2968    at: usize,
2969    /// The dialect, for the sentences a control word carries.
2970    d: Rules,
2971}
2972
2973impl<'a> AplCursor<'a> {
2974    fn peek(&self) -> Option<&'a AplItem> {
2975        self.items.get(self.at)
2976    }
2977
2978    fn peek_word(&self) -> Option<&'static str> {
2979        self.peek().and_then(AplItem::word)
2980    }
2981
2982    fn last_span(&self) -> Span {
2983        self.items
2984            .get(self.at.saturating_sub(1))
2985            .map_or_else(|| Span::new(0, 0), AplItem::span)
2986    }
2987
2988    /// Consume the closing word, which may also be spelled `:End`.
2989    fn close(&mut self, want: &str) -> Result<()> {
2990        match self.peek_word() {
2991            Some(w) if w == want || w == "End" => {
2992                self.at += 1;
2993                Ok(())
2994            }
2995            Some(w) => Err(Error::parse(
2996                format!("expected :{want} here, not :{w}"),
2997                self.peek().expect("a word").span(),
2998            )),
2999            None => Err(Error::parse(format!("this block needs a :{want}"), self.last_span())),
3000        }
3001    }
3002}
3003
3004fn parse_apl_block(cur: &mut AplCursor<'_>, stop: &[&str]) -> Result<Vec<Expr>> {
3005    let mut out = Vec::new();
3006    loop {
3007        match cur.peek() {
3008            None => return Ok(out),
3009            Some(AplItem::Word { word, .. }) if stop.contains(word) || *word == "End" => {
3010                return Ok(out);
3011            }
3012            Some(AplItem::Sentence(e)) => {
3013                cur.at += 1;
3014                out.push(e.clone());
3015            }
3016            Some(AplItem::Word { .. }) => out.push(parse_apl_control(cur)?),
3017        }
3018    }
3019}
3020
3021fn parse_apl_control(cur: &mut AplCursor<'_>) -> Result<Expr> {
3022    let Some(AplItem::Word { word, rest, span }) = cur.peek() else {
3023        return Err(Error::internal("expected a control word"));
3024    };
3025    let (word, rest, start) = (*word, rest.clone(), *span);
3026    cur.at += 1;
3027    let control = match word {
3028        "If" => {
3029            let mut arms = Vec::new();
3030            let mut otherwise = None;
3031            let mut test = rest;
3032            loop {
3033                let test_expr = condition(test, start, cur.d)?;
3034                let body = parse_apl_block(cur, &["ElseIf", "Else", "EndIf"])?;
3035                arms.push(Branch { test: Some(vec![test_expr]), body, fall_through: false });
3036                match cur.peek_word() {
3037                    Some("ElseIf") => {
3038                        let Some(AplItem::Word { rest, .. }) = cur.peek() else { unreachable!() };
3039                        test = rest.clone();
3040                        cur.at += 1;
3041                    }
3042                    Some("Else") => {
3043                        cur.at += 1;
3044                        otherwise = Some(parse_apl_block(cur, &["EndIf"])?);
3045                        cur.close("EndIf")?;
3046                        break;
3047                    }
3048                    _ => {
3049                        cur.close("EndIf")?;
3050                        break;
3051                    }
3052                }
3053            }
3054            Control::If { arms, otherwise }
3055        }
3056        "While" => {
3057            let test = condition(rest, start, cur.d)?;
3058            let body = parse_apl_block(cur, &["EndWhile"])?;
3059            cur.close("EndWhile")?;
3060            Control::While { test: vec![test], body, body_first: false, until: false }
3061        }
3062        "Repeat" => {
3063            if !rest.is_empty() {
3064                return Err(Error::parse(":Repeat takes no condition", start));
3065            }
3066            let body = parse_apl_block(cur, &["Until"])?;
3067            let Some(AplItem::Word { rest, span, .. }) = cur.peek() else {
3068                return Err(Error::parse("this :Repeat needs an :Until", cur.last_span()));
3069            };
3070            let test = condition(rest.clone(), *span, cur.d)?;
3071            cur.at += 1;
3072            Control::While { test: vec![test], body, body_first: true, until: true }
3073        }
3074        "For" => {
3075            // `:For name :In source`.
3076            let (name, source) = for_header(&rest, start, cur.d)?;
3077            let body = parse_apl_block(cur, &["EndFor"])?;
3078            cur.close("EndFor")?;
3079            Control::For { name: Some(name), source: Box::new(source), body }
3080        }
3081        "Select" => {
3082            let subject = condition(rest, start, cur.d)?;
3083            let mut cases = Vec::new();
3084            loop {
3085                match cur.peek() {
3086                    Some(AplItem::Word { word: "Case", rest, span }) => {
3087                        let test = condition(rest.clone(), *span, cur.d)?;
3088                        cur.at += 1;
3089                        let body = parse_apl_block(cur, &["Case", "Else", "EndSelect"])?;
3090                        cases.push(Branch {
3091                            test: Some(vec![test]),
3092                            body,
3093                            fall_through: false,
3094                        });
3095                    }
3096                    Some(AplItem::Word { word: "Else", .. }) => {
3097                        cur.at += 1;
3098                        let body = parse_apl_block(cur, &["EndSelect"])?;
3099                        cases.push(Branch { test: None, body, fall_through: false });
3100                        cur.close("EndSelect")?;
3101                        break;
3102                    }
3103                    _ => {
3104                        cur.close("EndSelect")?;
3105                        break;
3106                    }
3107                }
3108            }
3109            Control::Select { subject: Box::new(subject), cases }
3110        }
3111        "Return" => Control::Return,
3112        "Leave" => Control::Break,
3113        "Continue" => Control::Continue,
3114        other => {
3115            return Err(Error::parse(format!(":{other} has no matching opening word"), start));
3116        }
3117    };
3118    Ok(Expr::Control(Box::new(control), Span::merge(start, cur.last_span())))
3119}
3120
3121/// The tokens after a control word, as one expression.
3122fn condition(rest: Vec<Token>, span: Span, d: Rules) -> Result<Expr> {
3123    match rest.first() {
3124        None => Err(Error::parse("this control word needs a condition", span)),
3125        Some(first) => {
3126            let hint = Span::merge(first.span, rest[rest.len() - 1].span);
3127            match &rest[0].kind {
3128                Tok::Control(w) => Err(Error::parse(format!("unexpected :{w}"), rest[0].span)),
3129                _ => Ok(AplItem::Sentence(parse_prepared(&rest, hint, d)?)).map(|it| match it {
3130                    AplItem::Sentence(e) => e,
3131                    AplItem::Word { .. } => unreachable!(),
3132                }),
3133            }
3134        }
3135    }
3136}
3137
3138/// `:For name :In source`.
3139fn for_header(rest: &[Token], span: Span, d: Rules) -> Result<(String, Expr)> {
3140    let Some(Tok::Name(name)) = rest.first().map(|t| &t.kind) else {
3141        return Err(Error::parse(":For needs a name to bind", span));
3142    };
3143    let Some(k) = rest.iter().position(|t| matches!(t.kind, Tok::Control("In"))) else {
3144        return Err(Error::parse(":For needs an :In", span));
3145    };
3146    if k != 1 {
3147        return Err(Error::not_yet("several :For names", span));
3148    }
3149    let source = &rest[k + 1..];
3150    let Some(first) = source.first() else {
3151        return Err(Error::parse(":In needs a value", span));
3152    };
3153    let hint = Span::merge(first.span, source[source.len() - 1].span);
3154    Ok((name.clone(), parse_prepared(source, hint, d)?))
3155}
3156
3157/// Parse a token run that has already had its names and dfns folded.
3158fn parse_prepared(toks: &[Token], hint: Span, d: Rules) -> Result<Expr> {
3159    let toks = fold_axes(fold_operators(toks.to_vec(), d)?, d)?;
3160    if toks.is_empty() {
3161        return Err(Error::parse("this needs an expression", hint));
3162    }
3163    parse_range(&toks, 0, toks.len(), hint, d)
3164}
3165
3166/// `A[i;j]←v`: the one assignment that writes through a bracket. None when
3167/// the sentence is not one.
3168fn indexed_assignment(toks: &[Token], d: Rules, hint: Span) -> Result<Option<Expr>> {
3169    let Some(assign) = toks.iter().position(|t| matches!(t.kind, Tok::Assign)) else {
3170        return Ok(None);
3171    };
3172    if assign < 3 || !matches!(toks[assign - 1].kind, Tok::RBracket) {
3173        return Ok(None);
3174    }
3175    let close = assign - 1;
3176    let open = match_lbracket(toks, 0, close)?;
3177    if open == 0 {
3178        return Err(Error::parse("[ needs a value on its left", toks[open].span));
3179    }
3180    let Tok::Name(name) = &toks[open - 1].kind else {
3181        return Err(Error::not_yet("indexed assignment through an expression", hint));
3182    };
3183    if open != 1 {
3184        return Err(Error::not_yet("indexed assignment inside a larger sentence", hint));
3185    }
3186    let ranges = index_slots(toks, open + 1, close, toks[open].span)?;
3187    let mut slots = Vec::with_capacity(ranges.len());
3188    for slot in &ranges {
3189        slots.push(match *slot {
3190            None => None,
3191            Some((lo, hi)) => Some(parse_range(toks, lo, hi, toks[open].span, d)?),
3192        });
3193    }
3194    let value = parse_range(toks, assign + 1, toks.len(), toks[assign].span, d)?;
3195    let span = Span::merge(toks[0].span, toks[toks.len() - 1].span);
3196    Ok(Some(Expr::AmendIndex {
3197        name: name.clone(),
3198        slots,
3199        value: Box::new(value),
3200        origin: d.origin,
3201        scope: Scope::Local,
3202        span,
3203    }))
3204}
3205
3206/// Give every assignment in a `∇` definition's body its scope: local for
3207/// the names the header owns, global for the rest. Definitions nested
3208/// inside keep their own rules, so the walk stops at them.
3209fn set_scopes(e: &mut Expr, own: &[String]) {
3210    let pick = |name: &str| {
3211        if own.iter().any(|n| n == name) {
3212            Scope::Local
3213        } else {
3214            Scope::Global
3215        }
3216    };
3217    match e {
3218        Expr::Assign { name, value, scope, .. } => {
3219            *scope = pick(name);
3220            set_scopes(value, own);
3221        }
3222        Expr::AmendIndex { name, slots, value, scope, .. } => {
3223            *scope = pick(name);
3224            for slot in slots.iter_mut().flatten() {
3225                set_scopes(slot, own);
3226            }
3227            set_scopes(value, own);
3228        }
3229        Expr::Monad { y, .. } => set_scopes(y, own),
3230        Expr::Dyad { x, y, .. } => {
3231            set_scopes(x, own);
3232            set_scopes(y, own);
3233        }
3234        Expr::PrintPass { value, .. } => set_scopes(value, own),
3235        Expr::Input { .. } => {}
3236        Expr::Control(c, _) => {
3237            let walk = |b: &mut Vec<Expr>| b.iter_mut().for_each(|s| set_scopes(s, own));
3238            match &mut **c {
3239                Control::Branch(target) => set_scopes(target, own),
3240                Control::If { arms, otherwise } => {
3241                    for arm in arms {
3242                        if let Some(t) = &mut arm.test {
3243                            walk(t);
3244                        }
3245                        walk(&mut arm.body);
3246                    }
3247                    if let Some(b) = otherwise {
3248                        walk(b);
3249                    }
3250                }
3251                Control::While { test, body, .. } | Control::Guard { test, body } => {
3252                    walk(test);
3253                    walk(body);
3254                }
3255                Control::For { source, body, .. } => {
3256                    set_scopes(source, own);
3257                    walk(body);
3258                }
3259                Control::Select { subject, cases } => {
3260                    set_scopes(subject, own);
3261                    for case in cases {
3262                        if let Some(t) = &mut case.test {
3263                            walk(t);
3264                        }
3265                        walk(&mut case.body);
3266                    }
3267                }
3268                Control::Try { body, catch } => {
3269                    walk(body);
3270                    walk(catch);
3271                }
3272                Control::Return | Control::Break | Control::Continue => {}
3273            }
3274        }
3275        Expr::Const(..)
3276        | Expr::Param(..)
3277        | Expr::Name(..)
3278        | Expr::Fused { .. }
3279        | Expr::Elided { .. }
3280        | Expr::VerbDef { .. }
3281        | Expr::ModDef { .. } => {}
3282    }
3283}
3284
3285#[cfg(test)]
3286mod tests {
3287    use super::*;
3288    use crate::error::ErrorKind;
3289    use rstest::rstest;
3290
3291    /// The shipped dialect at the given index origin.
3292    fn rules(origin: i64) -> Rules {
3293        crate::Dialect { index_origin: Some(origin), ..crate::Dialect::default() }
3294            .rules(crate::Lang::Apl)
3295            .expect("the shipped dialect is implemented")
3296    }
3297
3298    /// Parse one source string with `⎕IO←1`.
3299    fn p(src: &str) -> Result<Vec<Expr>> {
3300        parse(&SourceParts::from_source(src).unwrap(), rules(1))
3301    }
3302
3303    fn one(src: &str) -> Expr {
3304        let mut stmts = p(src).unwrap_or_else(|e| panic!("{src}: {e}"));
3305        assert_eq!(stmts.len(), 1, "{src}: expected one sentence");
3306        stmts.pop().unwrap()
3307    }
3308
3309    fn err(src: &str) -> Error {
3310        match p(src) {
3311            Ok(_) => panic!("{src}: expected an error"),
3312            Err(e) => e,
3313        }
3314    }
3315
3316    fn as_const(e: &Expr) -> &Array {
3317        match e {
3318            Expr::Const(a, _) => a,
3319            other => panic!("expected a constant, got {other:?}"),
3320        }
3321    }
3322
3323    /// The primitive behind a verb, unwrapping nothing.
3324    fn as_prim(v: &Verb) -> Prim {
3325        match v {
3326            Verb::Prim(p) => *p,
3327            other => panic!("expected a primitive, got {other:?}"),
3328        }
3329    }
3330
3331    fn monad_of<'a>(e: &'a Expr, name: &str) -> &'a Expr {
3332        match e {
3333            Expr::Monad { verb, y, .. } => {
3334                assert_eq!(as_prim(verb).name, name, "monad name");
3335                y.as_ref()
3336            }
3337            other => panic!("expected a monad, got {other:?}"),
3338        }
3339    }
3340
3341    fn dyad_of<'a>(e: &'a Expr, name: &str) -> (&'a Expr, &'a Expr) {
3342        match e {
3343            Expr::Dyad { verb, x, y, .. } => {
3344                assert_eq!(as_prim(verb).name, name, "dyad name");
3345                (x.as_ref(), y.as_ref())
3346            }
3347            other => panic!("expected a dyad, got {other:?}"),
3348        }
3349    }
3350
3351    fn verb_of(e: &Expr) -> &Verb {
3352        match e {
3353            Expr::Monad { verb, .. } | Expr::Dyad { verb, .. } => verb,
3354            other => panic!("expected an application, got {other:?}"),
3355        }
3356    }
3357
3358    // --- literals ------------------------------------------------------
3359
3360    #[test]
3361    fn single_number_is_a_scalar() {
3362        let e = one("5");
3363        let a = as_const(&e);
3364        assert_eq!(a.shape, Vec::<usize>::new());
3365        assert_eq!(a.data, Data::I64(vec![5].into()));
3366    }
3367
3368    #[test]
3369    fn adjacent_numbers_merge_into_one_vector() {
3370        let a = as_const(&one("2 3 4")).clone();
3371        assert_eq!(a.shape, vec![3]);
3372        assert_eq!(a.data, Data::I64(vec![2, 3, 4].into()));
3373    }
3374
3375    #[test]
3376    fn one_float_makes_the_whole_vector_float() {
3377        let a = as_const(&one("1 2.5 3")).clone();
3378        assert_eq!(a.shape, vec![3]);
3379        assert_eq!(a.data, Data::F64(vec![1.0, 2.5, 3.0].into()));
3380    }
3381
3382    #[rstest]
3383    #[case("¯3", Data::I64(vec![-3].into()))]
3384    #[case("¯3.5", Data::F64(vec![-3.5].into()))]
3385    #[case("1e3", Data::I64(vec![1000].into()))]
3386    #[case("1e¯3", Data::F64(vec![0.001].into()))]
3387    #[case("2.5e2", Data::F64(vec![250.0].into()))]
3388    #[case("¯1 ¯2", Data::I64(vec![-1, -2].into()))]
3389    fn numeric_literals(#[case] src: &str, #[case] want: Data) {
3390        assert_eq!(as_const(&one(src)).data, want);
3391    }
3392
3393    #[test]
3394    fn single_char_string_is_rank_zero() {
3395        let a = as_const(&one("'a'")).clone();
3396        assert_eq!(a.shape, Vec::<usize>::new());
3397        assert_eq!(a.data, Data::Char(vec!['a'].into()));
3398    }
3399
3400    #[test]
3401    fn string_escape_doubles_the_quote() {
3402        let a = as_const(&one("'don''t'")).clone();
3403        assert_eq!(a.shape, vec![5]);
3404        assert_eq!(a.data, Data::Char("don't".chars().collect()));
3405    }
3406
3407    #[test]
3408    fn empty_string_is_an_empty_char_vector() {
3409        let a = as_const(&one("''")).clone();
3410        assert_eq!(a.shape, vec![0]);
3411        assert_eq!(a.data, Data::Char(vec![].into()));
3412    }
3413
3414    #[test]
3415    fn unterminated_string_is_a_parse_error() {
3416        let e = err("'abc");
3417        assert_eq!(e.kind, ErrorKind::Parse);
3418        assert!(e.msg.contains("unterminated"), "{}", e.msg);
3419    }
3420
3421    #[rstest]
3422    #[case("2j3", vec![[2.0, 3.0]])]
3423    #[case("1J¯1", vec![[1.0, -1.0]])]
3424    #[case("2 1j2", vec![[2.0, 0.0], [1.0, 2.0]])]
3425    fn complex_literals(#[case] src: &str, #[case] want: Vec<[f64; 2]>) {
3426        assert_eq!(as_const(&one(src)).data, Data::Complex(want.into()));
3427    }
3428
3429    // --- comments, sentences, names ------------------------------------
3430
3431    #[test]
3432    fn a_comment_runs_to_the_end_of_the_line() {
3433        let stmts = p("2+2 ⍝ a note ⋄ still a note\n3").unwrap();
3434        assert_eq!(stmts.len(), 2);
3435        dyad_of(&stmts[0], "+");
3436        assert_eq!(as_const(&stmts[1]).data, Data::I64(vec![3].into()));
3437    }
3438
3439    #[test]
3440    fn blank_sentences_are_skipped() {
3441        let stmts = p("\n\n2 ⋄ ⋄ 3 ⋄\n").unwrap();
3442        assert_eq!(stmts.len(), 2);
3443    }
3444
3445    #[test]
3446    fn diamond_and_newline_both_separate_sentences() {
3447        let stmts = p("x←3 ⋄ x+1").unwrap();
3448        assert_eq!(stmts.len(), 2);
3449        match &stmts[0] {
3450            Expr::Assign { name, value, .. } => {
3451                assert_eq!(name, "x");
3452                assert_eq!(as_const(value).data, Data::I64(vec![3].into()));
3453            }
3454            other => panic!("expected an assignment, got {other:?}"),
3455        }
3456        let (x, y) = dyad_of(&stmts[1], "+");
3457        assert!(matches!(x, Expr::Name(n, _) if n == "x"));
3458        assert_eq!(as_const(y).data, Data::I64(vec![1].into()));
3459    }
3460
3461    #[rstest]
3462    #[case("x")]
3463    #[case("abc123")]
3464    #[case("∆x")]
3465    #[case("⍙y_2")]
3466    #[case("Σ")]
3467    fn names(#[case] src: &str) {
3468        match one(src) {
3469            Expr::Name(n, _) => assert_eq!(n, src),
3470            other => panic!("expected a name, got {other:?}"),
3471        }
3472    }
3473
3474    #[test]
3475    fn unknown_symbol_is_reported_with_its_position() {
3476        let e = err("2 @ 3");
3477        assert_eq!(e.kind, ErrorKind::Parse);
3478        assert_eq!(e.msg, "unknown symbol: @");
3479        assert_eq!(e.span, Some(Span::new(2, 3)));
3480    }
3481
3482    #[test]
3483    fn system_variables_are_read_only() {
3484        // Read-only is permanent, not a queue position: the dialect fixed
3485        // these before the program was compiled.
3486        let e = err("⎕IO←0");
3487        assert_eq!(e.kind, ErrorKind::Language);
3488        assert!(e.msg.contains("read-only"), "{}", e.msg);
3489        // The ones that would reach outside the program are refused by
3490        // name, whether they are read or written.
3491        let e = err("⎕TS");
3492        assert_eq!(e.kind, ErrorKind::Sandbox);
3493        assert!(e.msg.contains("outside the program"), "{}", e.msg);
3494    }
3495
3496    // --- the primitive table -------------------------------------------
3497
3498    #[rstest]
3499    #[case('+', MonadOp::Scalar(ScalarMonad::Conj), DyadOp::Scalar(ScalarDyad::Add))]
3500    #[case('-', MonadOp::Scalar(ScalarMonad::Neg), DyadOp::Scalar(ScalarDyad::Sub))]
3501    #[case('×', MonadOp::Scalar(ScalarMonad::Signum), DyadOp::Scalar(ScalarDyad::Mul))]
3502    #[case('÷', MonadOp::Scalar(ScalarMonad::Recip), DyadOp::Scalar(ScalarDyad::DivApl))]
3503    #[case('⌈', MonadOp::Scalar(ScalarMonad::Ceil), DyadOp::Scalar(ScalarDyad::Max))]
3504    #[case('⌊', MonadOp::Scalar(ScalarMonad::Floor), DyadOp::Scalar(ScalarDyad::Min))]
3505    #[case('*', MonadOp::Scalar(ScalarMonad::Exp), DyadOp::Scalar(ScalarDyad::Pow))]
3506    #[case('|', MonadOp::Scalar(ScalarMonad::Abs), DyadOp::Scalar(ScalarDyad::Residue))]
3507    #[case('=', MonadOp::None, DyadOp::Scalar(ScalarDyad::Eq))]
3508    #[case('<', MonadOp::None, DyadOp::Scalar(ScalarDyad::Lt))]
3509    #[case('≤', MonadOp::None, DyadOp::Scalar(ScalarDyad::Le))]
3510    #[case('>', MonadOp::None, DyadOp::Scalar(ScalarDyad::Gt))]
3511    #[case('≥', MonadOp::None, DyadOp::Scalar(ScalarDyad::Ge))]
3512    #[case('⍴', MonadOp::ShapeOf, DyadOp::Reshape)]
3513    #[case('⍉', MonadOp::TransposeAxes, DyadOp::TransposeApl)]
3514    #[case(',', MonadOp::Ravel, DyadOp::AppendLast)]
3515    #[case('⍪', MonadOp::TableOf, DyadOp::AppendLeading)]
3516    #[case('!', MonadOp::Scalar(ScalarMonad::Factorial), DyadOp::Scalar(ScalarDyad::Binomial))]
3517    #[case('⍕', MonadOp::Format, DyadOp::FormatSpec)]
3518    #[case('⊥', MonadOp::None, DyadOp::DecodeApl)]
3519    #[case('⊤', MonadOp::None, DyadOp::EncodeApl)]
3520    #[case('≢', MonadOp::Tally, DyadOp::NotMatch)]
3521    #[case('≡', MonadOp::Depth { signed: false }, DyadOp::Match)]
3522    #[case('∊', MonadOp::Enlist, DyadOp::MemberApl)]
3523    #[case('∪', MonadOp::Nub, DyadOp::Union)]
3524    #[case('∧', MonadOp::None, DyadOp::Scalar(ScalarDyad::Lcm))]
3525    #[case('∨', MonadOp::None, DyadOp::Scalar(ScalarDyad::Gcd))]
3526    #[case('⍟', MonadOp::Scalar(ScalarMonad::Ln), DyadOp::Scalar(ScalarDyad::Log))]
3527    #[case('~', MonadOp::Scalar(ScalarMonad::Not), DyadOp::Less)]
3528    #[case('⊖', MonadOp::Reverse, DyadOp::RotateApl { last: false })]
3529    #[case('⍋', MonadOp::GradeUp { origin: 1 }, DyadOp::CollateGrade { down: false, origin: 1 })]
3530    #[case('⍒', MonadOp::GradeDown { origin: 1 }, DyadOp::CollateGrade { down: true, origin: 1 })]
3531    #[case('⊢', MonadOp::Same, DyadOp::Right)]
3532    #[case('⊣', MonadOp::Same, DyadOp::Left)]
3533    #[case('↑', MonadOp::First, DyadOp::Take)]
3534    #[case('⊂', MonadOp::Enclose(Enclose::ExceptSimpleScalar), DyadOp::PartitionEnclose)]
3535    #[case('⊃', MonadOp::Open, DyadOp::Pick { origin: 1 })]
3536    #[case('↓', MonadOp::Split, DyadOp::Drop)]
3537    fn primitive_meanings(#[case] glyph: char, #[case] monad: MonadOp, #[case] dyad: DyadOp) {
3538        let src = format!("{glyph}1");
3539        let e = one(&src);
3540        match e {
3541            Expr::Monad { verb, .. } => {
3542                let prim = as_prim(&verb);
3543                assert_eq!(prim.monad, monad);
3544                assert_eq!(prim.dyad, dyad);
3545                assert_eq!(prim.name.chars().next(), Some(glyph));
3546            }
3547            other => panic!("expected a monad, got {other:?}"),
3548        }
3549    }
3550
3551    #[test]
3552    fn monadic_not_equal_is_the_nub_sieve() {
3553        let e = one("≠1");
3554        match e {
3555            Expr::Monad { verb, .. } => {
3556                assert_eq!(as_prim(&verb).monad, MonadOp::NubSieve);
3557            }
3558            other => panic!("expected a monad, got {other:?}"),
3559        }
3560    }
3561
3562    #[test]
3563    fn monadic_equals_parses_and_is_left_to_evaluation() {
3564        // `=` has no monadic meaning; the parser accepts it and eval refuses.
3565        let e = one("=1");
3566        assert_eq!(as_prim(verb_of(&e)).monad, MonadOp::None);
3567    }
3568
3569    #[rstest]
3570    #[case(0)]
3571    #[case(1)]
3572    fn iota_carries_the_index_origin(#[case] origin: i64) {
3573        let sp = SourceParts::from_source("⍳3").unwrap();
3574        let stmts = parse(&sp, rules(origin)).unwrap();
3575        match &stmts[0] {
3576            Expr::Monad { verb, .. } => {
3577                assert_eq!(as_prim(verb).monad, MonadOp::IotaApl { origin });
3578                assert_eq!(as_prim(verb).dyad, DyadOp::IndexOf { origin, vector_left: false });
3579                assert_eq!(as_prim(verb).ranks, [RANK_INF, RANK_INF, RANK_INF]);
3580            }
3581            other => panic!("expected a monad, got {other:?}"),
3582        }
3583    }
3584
3585    #[test]
3586    fn reverse_and_rotate_pick_their_axis() {
3587        // `⌽` is `⊖` on rows: the rank operator supplies the axis for the
3588        // MONAD, and the dyad picks the last axis itself.
3589        let e = one("⌽2 3⍴⍳6");
3590        match verb_of(&e) {
3591            Verb::Rank(f, ranks) => {
3592                assert_eq!(*ranks, [1, RANK_INF, RANK_INF]);
3593                assert_eq!(as_prim(f).monad, MonadOp::Reverse);
3594                assert_eq!(as_prim(f).dyad, DyadOp::RotateApl { last: true });
3595            }
3596            other => panic!("expected a ranked verb, got {other:?}"),
3597        }
3598        // `⊖` is the primitive itself, on the leading axis.
3599        assert!(matches!(verb_of(&one("⊖2 3⍴⍳6")), Verb::Prim(_)));
3600    }
3601
3602    #[test]
3603    fn reshape_ranks_are_infinite_one_infinite() {
3604        let e = one("2 3⍴⍳6");
3605        assert_eq!(verb_of(&e).ranks(), [RANK_INF, 1, RANK_INF]);
3606    }
3607
3608    // --- right-to-left parsing -----------------------------------------
3609
3610    #[test]
3611    fn reshape_of_iota() {
3612        let e = one("2 3⍴⍳6");
3613        let (x, y) = dyad_of(&e, "⍴");
3614        assert_eq!(as_const(x).data, Data::I64(vec![2, 3].into()));
3615        let iy = monad_of(y, "⍳");
3616        assert_eq!(as_const(iy).data, Data::I64(vec![6].into()));
3617    }
3618
3619    #[test]
3620    fn leading_minus_is_monadic_and_the_rest_is_evaluated_first() {
3621        // Right to left: `-3+4` is negate (3+4), not (-3)+4.
3622        let e = one("-3+4");
3623        let inner = monad_of(&e, "-");
3624        let (x, y) = dyad_of(inner, "+");
3625        assert_eq!(as_const(x).data, Data::I64(vec![3].into()));
3626        assert_eq!(as_const(y).data, Data::I64(vec![4].into()));
3627    }
3628
3629    #[test]
3630    fn a_chain_of_dyads_associates_to_the_right() {
3631        let e = one("2×3+4");
3632        let (x, y) = dyad_of(&e, "×");
3633        assert_eq!(as_const(x).data, Data::I64(vec![2].into()));
3634        dyad_of(y, "+");
3635    }
3636
3637    #[test]
3638    fn parentheses_override_the_order() {
3639        let e = one("(2+3)×4");
3640        let (x, y) = dyad_of(&e, "×");
3641        dyad_of(x, "+");
3642        assert_eq!(as_const(y).data, Data::I64(vec![4].into()));
3643    }
3644
3645    #[test]
3646    fn nested_parentheses() {
3647        let e = one("((2+3))×4");
3648        let (x, _) = dyad_of(&e, "×");
3649        dyad_of(x, "+");
3650    }
3651
3652    #[test]
3653    fn a_function_left_of_a_function_is_monadic() {
3654        // `⍴⍳5`: shape of iota, both monadic.
3655        let e = one("⍴⍳5");
3656        monad_of(monad_of(&e, "⍴"), "⍳");
3657    }
3658
3659    // --- operators ------------------------------------------------------
3660
3661    #[test]
3662    fn slash_reduces_the_last_axis() {
3663        // APL `+/` is J's `+/"1` monadically — rank 1 over the reduction —
3664        // but a different function dyadically, so the node is its own. The
3665        // left cell is unranked: n is one number, never a frame of them.
3666        let e = one("+/2 3⍴⍳6");
3667        match &e {
3668            Expr::Monad { verb: Verb::Rank(inner, ranks), .. } => {
3669                assert_eq!(*ranks, [1, RANK_INF, 1]);
3670                match inner.as_ref() {
3671                    Verb::NWise(f) => assert_eq!(as_prim(f).name, "+"),
3672                    other => panic!("expected a reduce, got {other:?}"),
3673                }
3674            }
3675            other => panic!("expected monadic Rank(NWise(+)), got {other:?}"),
3676        }
3677    }
3678
3679    #[test]
3680    fn slashbar_reduces_the_leading_axis() {
3681        let e = one("+⌿2 3⍴⍳6");
3682        match &e {
3683            Expr::Monad { verb: Verb::NWise(f), .. } => assert_eq!(as_prim(f).name, "+"),
3684            other => panic!("expected monadic NWise(+), got {other:?}"),
3685        }
3686    }
3687
3688    #[test]
3689    fn backslash_scans_the_last_axis_and_backslashbar_the_leading_one() {
3690        // The k-th element of a scan is the REDUCE of the first k, so the
3691        // derived verb applies `f/` to every prefix, not `f`.
3692        let inner = |v: &Verb| match v {
3693            Verb::Windowed(g, WindowKind::Scan) => match &**g {
3694                Verb::Reduce(h) => as_prim(h).name,
3695                other => panic!("expected a reduction under the scan, got {other:?}"),
3696            },
3697            other => panic!("expected a scan, got {other:?}"),
3698        };
3699        match &one("+\\1 2 3") {
3700            Expr::Monad { verb: Verb::Rank(f, ranks), .. } => {
3701                assert_eq!(*ranks, [1, 1, 1]);
3702                assert_eq!(inner(f), "+");
3703            }
3704            other => panic!("expected a ranked scan, got {other:?}"),
3705        }
3706        match &one("+⍀1 2 3") {
3707            Expr::Monad { verb, .. } => assert_eq!(inner(verb), "+"),
3708            other => panic!("expected a leading-axis scan, got {other:?}"),
3709        }
3710    }
3711
3712    /// After an operand `/` and `⌿` are replicate, the function — the two
3713    /// readings are told apart by the token on the left and nothing else.
3714    #[rstest]
3715    #[case("1 0 1/1 2 3", "/")]
3716    #[case("1 0 1⌿1 2 3", "⌿")]
3717    #[case("x/1 2 3", "/")]
3718    #[case("(1 0)/1 2 3", "/")]
3719    fn slash_after_an_operand_is_replicate(#[case] src: &str, #[case] name: &str) {
3720        let e = one(src);
3721        let (_, _) = dyad_of(&e, name);
3722        assert_eq!(as_prim(verb_of(&e)).dyad, DyadOp::Copy);
3723    }
3724
3725    #[rstest]
3726    #[case("1 0 1\\1 2 3")]
3727    #[case("1 0 1⍀1 2 3")]
3728    fn expand_after_a_value_is_a_function(#[case] src: &str) {
3729        let e = one(src);
3730        assert_eq!(as_prim(verb_of(&e)).dyad, DyadOp::Expand);
3731    }
3732
3733    #[test]
3734    fn commute_and_power_are_operators() {
3735        match one("2-⍨5") {
3736            Expr::Dyad { verb: Verb::Commute(f), .. } => assert_eq!(as_prim(&f).name, "-"),
3737            other => panic!("expected a commute, got {other:?}"),
3738        }
3739        match one("+⍣3⊢5") {
3740            Expr::Monad { verb: Verb::PowerN(_, p), .. } => assert_eq!(p, Power::Times(3)),
3741            other => panic!("expected a power, got {other:?}"),
3742        }
3743        match one("+⍣≡⊢5") {
3744            Expr::Monad { verb: Verb::PowerUntil(..), .. } => {}
3745            other => panic!("expected a power until, got {other:?}"),
3746        }
3747        // A negative count is the inverse applied that many times, over
3748        // the obverse table; a verb with no inverse says which one.
3749        match one("⌽⍣¯1⊢5") {
3750            Expr::Monad { verb: Verb::PowerN(_, p), .. } => assert_eq!(p, Power::Times(1)),
3751            other => panic!("expected a power, got {other:?}"),
3752        }
3753        let e = err("⍴⍣¯1⊢5");
3754        assert_eq!(e.kind, ErrorKind::NotYet);
3755        assert!(e.msg.contains("obverse"), "{}", e.msg);
3756    }
3757
3758    #[rstest]
3759    #[case("+⍤2⊢5", [2, 2, 2])]
3760    #[case("+⍤1 2⊢5", [2, 1, 2])]
3761    #[case("+⍤0 1 2⊢5", [0, 1, 2])]
3762    #[case("+⍤¯1⊢5", [-1, -1, -1])]
3763    fn rank_operator_spec(#[case] src: &str, #[case] want: [i64; 3]) {
3764        let e = one(src);
3765        match &e {
3766            Expr::Monad { verb: Verb::Rank(f, ranks), .. } => {
3767                assert_eq!(*ranks, want);
3768                assert_eq!(as_prim(f).name, "+");
3769            }
3770            other => panic!("expected monadic Rank(+), got {other:?}"),
3771        }
3772    }
3773
3774    #[test]
3775    fn rank_operator_stacks_on_a_derived_function() {
3776        let e = one("+/⍤1⊢5");
3777        match &e {
3778            Expr::Monad { verb: Verb::Rank(inner, ranks), .. } => {
3779                assert_eq!(*ranks, [1, 1, 1]);
3780                assert!(matches!(inner.as_ref(), Verb::Rank(_, [1, RANK_INF, 1])));
3781            }
3782            other => panic!("expected Rank(Rank(NWise(+))), got {other:?}"),
3783        }
3784    }
3785
3786    #[test]
3787    fn a_function_operand_makes_the_rank_operator_an_atop() {
3788        // `f⍤g` with a function on the right is Dyalog's atop, not a rank.
3789        let e = one("+⍤×5");
3790        let Expr::Monad { verb, .. } = e else { panic!("expected a monad") };
3791        assert!(matches!(verb, Verb::Atop(..)), "{verb:?}");
3792    }
3793
3794    #[rstest]
3795    #[case("+⍤0 1 2 3⊢5", "1 to 3")]
3796    #[case("+⍤", "rank specification")]
3797    #[case("+⍤2.5⊢5", "must be integers")]
3798    #[case("+⍤'a'⊢5", "must be integers")]
3799    fn bad_rank_specifications(#[case] src: &str, #[case] fragment: &str) {
3800        let e = err(src);
3801        assert_eq!(e.kind, ErrorKind::Parse);
3802        assert!(e.msg.contains(fragment), "{}", e.msg);
3803    }
3804
3805    // --- assignment and output -----------------------------------------
3806
3807    #[test]
3808    fn quad_arrow_is_print_pass() {
3809        let e = one("⎕←2+2");
3810        match &e {
3811            Expr::PrintPass { value, .. } => {
3812                dyad_of(value, "+");
3813            }
3814            other => panic!("expected PrintPass, got {other:?}"),
3815        }
3816    }
3817
3818    #[test]
3819    fn assignment_chains() {
3820        let e = one("a←b←5");
3821        match &e {
3822            Expr::Assign { name, value, .. } => {
3823                assert_eq!(name, "a");
3824                match value.as_ref() {
3825                    Expr::Assign { name, value, .. } => {
3826                        assert_eq!(name, "b");
3827                        assert_eq!(as_const(value).data, Data::I64(vec![5].into()));
3828                    }
3829                    other => panic!("expected a nested assignment, got {other:?}"),
3830                }
3831            }
3832            other => panic!("expected an assignment, got {other:?}"),
3833        }
3834    }
3835
3836    #[test]
3837    fn assignment_inside_an_expression() {
3838        let e = one("2+a←3");
3839        let (x, y) = dyad_of(&e, "+");
3840        assert_eq!(as_const(x).data, Data::I64(vec![2].into()));
3841        match y {
3842            Expr::Assign { name, value, .. } => {
3843                assert_eq!(name, "a");
3844                assert_eq!(as_const(value).data, Data::I64(vec![3].into()));
3845            }
3846            other => panic!("expected an assignment, got {other:?}"),
3847        }
3848    }
3849
3850    #[rstest]
3851    #[case("2←3")]
3852    #[case("(2+2)←3")]
3853    fn assignment_target_must_be_a_name(#[case] src: &str) {
3854        let e = err(src);
3855        assert_eq!(e.kind, ErrorKind::Parse);
3856        assert_eq!(e.msg, "assignment target must be a name");
3857    }
3858
3859    // --- parameters -----------------------------------------------------
3860
3861    #[test]
3862    fn a_parameter_hole_is_an_operand() {
3863        let sp = SourceParts::from_parts(&["", "+1"], &["x"]);
3864        let stmts = parse(&sp, rules(1)).unwrap();
3865        let (x, y) = dyad_of(&stmts[0], "+");
3866        assert!(matches!(x, Expr::Param(0, _)));
3867        assert_eq!(as_const(y).data, Data::I64(vec![1].into()));
3868        // `{x}` occupies the first three characters of the display source.
3869        assert_eq!(x.span(), Span::new(0, 3));
3870        assert_eq!(sp.display, "{x}+1");
3871    }
3872
3873    #[test]
3874    fn a_parameter_can_be_reduced_over() {
3875        let sp = SourceParts::from_parts(&["+/", ""], &["m"]);
3876        let stmts = parse(&sp, rules(1)).unwrap();
3877        match &stmts[0] {
3878            Expr::Monad { verb: Verb::Rank(_, [1, RANK_INF, 1]), y, .. } => {
3879                assert!(matches!(y.as_ref(), Expr::Param(0, _)));
3880            }
3881            other => panic!("expected a reduction over a parameter, got {other:?}"),
3882        }
3883    }
3884
3885    #[test]
3886    fn a_parameter_inside_a_comment_is_dropped() {
3887        let sp = SourceParts::from_parts(&["1 ⍝ ", "\n2"], &["x"]);
3888        let stmts = parse(&sp, rules(1)).unwrap();
3889        assert_eq!(stmts.len(), 2);
3890        assert_eq!(as_const(&stmts[0]).data, Data::I64(vec![1].into()));
3891        assert_eq!(as_const(&stmts[1]).data, Data::I64(vec![2].into()));
3892    }
3893
3894    // --- spans ----------------------------------------------------------
3895
3896    #[test]
3897    fn nodes_cover_their_source_extent() {
3898        let src = "2 3⍴⍳6";
3899        let e = one(src);
3900        assert_eq!(e.span(), Span::new(0, src.len()));
3901        let (x, y) = dyad_of(&e, "⍴");
3902        assert_eq!(x.span(), Span::new(0, 3));
3903        // `⍳6` starts after `2 3⍴`: three ASCII bytes plus a three-byte glyph.
3904        assert_eq!(y.span(), Span::new(6, src.len()));
3905    }
3906
3907    #[test]
3908    fn spans_of_a_later_sentence_are_absolute() {
3909        let src = "x←3 ⋄ x+1";
3910        let stmts = p(src).unwrap();
3911        // `←` and `⋄` are three bytes each, so the second `x` sits at byte 10.
3912        assert_eq!(&src[10..], "x+1");
3913        assert_eq!(stmts[1].span(), Span::new(10, src.len()));
3914    }
3915
3916    #[test]
3917    fn a_dyad_span_includes_the_parenthesised_left_argument() {
3918        let src = "(2+3)×4";
3919        let e = one(src);
3920        assert_eq!(e.span(), Span::new(0, src.len()));
3921    }
3922
3923    // --- syntax errors --------------------------------------------------
3924
3925    /// Juxtaposition is vector notation: the operands become the items of
3926    /// one vector, and the whole strand is a single operand.
3927    #[rstest]
3928    #[case("(2 3)(4 5)", 2)]
3929    #[case("2 x", 2)]
3930    #[case("x y", 2)]
3931    #[case("2(3)", 2)]
3932    #[case("1 2 (3 4)", 3)]
3933    #[case("'ab' 'cd' 'ef'", 3)]
3934    fn juxtaposition_is_vector_notation(#[case] src: &str, #[case] items: usize) {
3935        // The strand is built right to left: one seeding monad and one
3936        // dyad per item after the first.
3937        let mut e = &one(src);
3938        for _ in 0..items - 1 {
3939            match e {
3940                Expr::Dyad { verb, y, .. } => {
3941                    assert_eq!(verb.name(), "(vector notation)", "{src}");
3942                    e = y.as_ref();
3943                }
3944                other => panic!("{src}: expected a strand, got {other:?}"),
3945            }
3946        }
3947        assert!(matches!(e, Expr::Monad { .. }), "{src}: {e:?}");
3948    }
3949
3950    #[rstest]
3951    #[case("2+", "missing right argument")]
3952    #[case("x←", "← needs a value")]
3953    #[case("(2+3", "syntax error")]
3954    #[case("2+3)", "unmatched )")]
3955    #[case("()", "empty parentheses")]
3956        #[case("/2 3", "needs a function to its left")]
3957    fn syntax_errors(#[case] src: &str, #[case] fragment: &str) {
3958        let e = err(src);
3959        assert_eq!(e.kind, ErrorKind::Parse);
3960        assert!(e.msg.contains(fragment), "{src}: {}", e.msg);
3961    }
3962
3963    #[test]
3964    fn empty_source_has_no_statements() {
3965        assert!(p("").unwrap().is_empty());
3966        assert!(p("  ⍝ nothing here\n").unwrap().is_empty());
3967    }
3968
3969    /// Every APL expression the evaluation suite runs must at least parse.
3970    #[rstest]
3971    #[case("2+2")]
3972    #[case("¯2×3")]
3973    #[case("-3+4")]
3974    #[case("0÷0")]
3975    #[case("⍳4")]
3976    #[case("⍳0")]
3977    #[case("2 3⍴⍳6")]
3978    #[case("⍴2 3⍴⍳6")]
3979    #[case("⍉2 3⍴⍳6")]
3980    #[case("≢7 8 9")]
3981    #[case("2↑9 8 7")]
3982    #[case("¯2↑9 8 7")]
3983    #[case("1↓3 3⍴⍳9")]
3984    #[case(",2 2⍴⍳4")]
3985    #[case("x←3 ⋄ x+1")]
3986    #[case("2+a←3")]
3987    #[case("⎕←2+2")]
3988    #[case("(2 3⍴⍳6)+10 20")]
3989    #[case("2+3 ⍝ sum")]
3990    #[case("+/2 3⍴⍳6")]
3991    #[case("+⌿2 3⍴⍳6")]
3992    #[case("⎕←'Hello, world!'")]
3993    fn the_evaluation_corpus_parses(#[case] src: &str) {
3994        p(src).unwrap_or_else(|e| panic!("{src}: {e}"));
3995    }
3996
3997    #[test]
3998    fn errors_render_against_the_display_source() {
3999        let src = "2 3⍴⍳6\n2 @ 3";
4000        let e = err(src);
4001        let rendered = e.render(src);
4002        assert!(rendered.contains("unknown symbol: @"), "{rendered}");
4003        assert!(rendered.contains("2 @ 3"), "{rendered}");
4004    }
4005}