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