Skip to main content

jay/frontend/
apl.rs

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