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