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