Skip to main content

jay/
ir.rs

1//! The language-agnostic program representation and its evaluator.
2
3use std::collections::HashMap;
4use std::sync::Arc;
5
6use crate::array::{Array, Data};
7use crate::error::{Error, ErrorKind, Result, Span};
8use crate::fmt::{format_array, FmtOpts};
9use crate::frontend::Rules;
10use crate::fuse::FusedKernel;
11use crate::verb::{arrays_match, Agreement, Ctx, Env, EvalCfg, Verb};
12
13/// Where an assignment puts its name. The two differ only inside an
14/// explicit definition, which is the only thing that has a local frame.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum Scope {
17    /// J `=.`, APL's default inside a definition: the running definition's
18    /// own frame, discarded when it returns.
19    Local,
20    /// J `=:`: the program's names, visible to everything that runs later.
21    Global,
22    /// APL `⍺←`: the local frame, but only where the name has no value yet.
23    /// A left argument that was supplied keeps the value it arrived with.
24    LocalDefault,
25}
26
27#[derive(Clone, Debug)]
28pub enum Expr {
29    Const(Array, Span),
30    /// A bound parameter, by position in `Program::params`.
31    Param(usize, Span),
32    /// A name assigned earlier in the same program.
33    Name(String, Span),
34    /// Yields the assigned value in expression position; a whole sentence
35    /// that is an assignment displays nothing at the top level.
36    Assign { name: String, value: Box<Expr>, scope: Scope, span: Span },
37    /// APL `A[i;j]←v`: the named value with the part the brackets select
38    /// replaced. The name is read, a copy is written, and the copy takes
39    /// the name's place. An elided slot selects its whole axis.
40    AmendIndex {
41        name: String,
42        slots: Vec<Option<Expr>>,
43        value: Box<Expr>,
44        origin: i64,
45        scope: Scope,
46        span: Span,
47    },
48    /// A control-flow sentence (J's control words, APL's `:If` family).
49    /// Its value is the value of the last sentence the branch it chose
50    /// executed. Only an explicit definition's body holds one: neither
51    /// language allows a control word outside a definition.
52    Control(Box<Control>, Span),
53    Monad { verb: Verb, y: Box<Expr>, span: Span },
54    Dyad { verb: Verb, x: Box<Expr>, y: Box<Expr>, span: Span },
55    /// APL `⎕← expr`: print, pass the value through.
56    PrintPass { value: Box<Expr>, span: Span },
57    /// A chain of elementwise verbs evaluated in one blockwise pass (see
58    /// [`crate::fuse`]). `inputs` are the subtrees the chain reads; `orig`
59    /// is the chain itself, which runs whenever the kernel declines.
60    Fused { kernel: FusedKernel, inputs: Vec<Expr>, orig: Box<Expr>, span: Span },
61    /// A marker the fusion pass leaves when it has rewritten the program
62    /// across sentence boundaries: it does nothing and yields nothing, and
63    /// carries the sentences the program was compiled from so that
64    /// [`crate::fuse::unfused`] can rebuild them.
65    Elided { orig: Vec<Expr>, span: Span },
66    /// A sentence that named a verb (J `mean =. +/ % #`). The frontend has
67    /// already substituted the verb into the later sentences that use the
68    /// name, so nothing runs here; the node is kept so that the sentence
69    /// still yields no value, and so that [`Program::explain`] can show it.
70    VerbDef { name: String, verb: Verb, span: Span },
71}
72
73/// A control-flow sentence. Every body is a block: a list of sentences whose
74/// value is the last one's.
75#[derive(Clone, Debug)]
76pub enum Control {
77    /// `if. T do. B elseif. T do. B else. B end.`, and APL's `:If` family.
78    /// The arms are tested in order; `otherwise` is the `else.` body.
79    If { arms: Vec<Branch>, otherwise: Option<Vec<Expr>> },
80    /// `while.` and `whilst.`, APL's `:While` and `:Repeat`. `body_first`
81    /// runs the body once before the first test; `until` inverts the test.
82    While { test: Vec<Expr>, body: Vec<Expr>, body_first: bool, until: bool },
83    /// `for. y do. B end.` / `for_i.` / `:For i :In y`. `name` binds each
84    /// item and `<name>_index` its position.
85    For { name: Option<String>, source: Box<Expr>, body: Vec<Expr> },
86    /// `select. T case. S do. B end.` and `:Select`. A case with no test is
87    /// the default (`case. do.`, `:Else`); `fall_through` is `fcase.`.
88    Select { subject: Box<Expr>, cases: Vec<Branch> },
89    /// `try. B catch. B end.`. The catch block runs on a language error;
90    /// a gap in libjay itself is never caught.
91    Try { body: Vec<Expr>, catch: Vec<Expr> },
92    /// `return.` / `:Return`: leave the definition with the value in hand.
93    Return,
94    /// `break.` / `:Leave`: leave the innermost loop.
95    Break,
96    /// APL `→ e`: continue at the line e names. An empty value falls
97    /// through to the next line; anything that is not a line of this
98    /// definition — `→0` above all — leaves it.
99    Branch(Box<Expr>),
100    /// `continue.` / `:Continue`: start the innermost loop's next iteration.
101    Continue,
102}
103
104/// One arm of an `if.` or `select.`: a test (absent for the default arm) and
105/// the body to run when it holds.
106#[derive(Clone, Debug)]
107pub struct Branch {
108    pub test: Option<Vec<Expr>>,
109    pub body: Vec<Expr>,
110    /// `fcase.`: run the next arm's body too, without testing it.
111    pub fall_through: bool,
112}
113
114/// The right-argument name a NILADIC APL definition carries. No sentence
115/// can write it, so the body cannot read the argument it never gets, and a
116/// definition wearing it is called by naming it rather than applying it.
117pub const NILADIC: &str = "(no argument)";
118
119/// An explicit definition: J's `3 : '…'`, `4 : '…'` and `{{ … }}`, APL's
120/// `{…}` and `∇`-defined functions.
121#[derive(Debug)]
122pub struct ExplicitDef {
123    /// How the definition names itself in diagnostics and `explain`.
124    pub name: String,
125    /// The names the arguments arrive under: `(left, right)`. A definition
126    /// with no left name has no dyadic valence.
127    pub left: Option<String>,
128    pub right: String,
129    /// True where a left name is part of the definition's valence rather
130    /// than a name the body may or may not read: J's `4 : '…'` and a `{{ }}`
131    /// that mentions `x` are dyads and nothing else, while an APL dfn that
132    /// names `⍺` still runs monadically and finds `⍺` undefined.
133    pub dyad_only: bool,
134    /// The name the result is read from when the body does not yield one
135    /// (an APL `∇`-definition's `Z←`); None means the body's own value.
136    pub result: Option<String>,
137    /// Names the header declares local (APL's `;name` list).
138    pub locals: Vec<String>,
139    pub body: Vec<Expr>,
140    /// The value a body that ran nothing yields; None makes that an error.
141    pub empty: Option<Array>,
142    /// APL's branch labels: each label with the body statement it names.
143    /// A label's value is its line number, which is one more than its
144    /// position here, and `→` takes one of those numbers.
145    pub labels: Vec<(String, usize)>,
146    /// True when running the body can have no effect beyond its result.
147    pub pure: bool,
148}
149
150impl Expr {
151    /// How deeply this tree nests, counted WITHOUT recursing — the point
152    /// of the measurement is that walking such a tree is what runs out of
153    /// stack, so the measurement itself must not.
154    pub(crate) fn depth(&self) -> usize {
155        let mut deepest = 0usize;
156        let mut stack: Vec<(&Expr, usize)> = vec![(self, 1)];
157        while let Some((e, d)) = stack.pop() {
158            deepest = deepest.max(d);
159            let kids: Vec<&Expr> = match e {
160                Expr::Const(..)
161                | Expr::Param(..)
162                | Expr::Name(..)
163                | Expr::Control(..)
164                | Expr::VerbDef { .. } => Vec::new(),
165                Expr::Assign { value, .. } | Expr::PrintPass { value, .. } => vec![value],
166                Expr::AmendIndex { slots, value, .. } => {
167                    slots.iter().flatten().chain(std::iter::once(&**value)).collect()
168                }
169                Expr::Monad { y, .. } => vec![y],
170                Expr::Dyad { x, y, .. } => vec![x, y],
171                Expr::Fused { inputs, orig, .. } => {
172                    inputs.iter().chain(std::iter::once(&**orig)).collect()
173                }
174                Expr::Elided { orig, .. } => orig.iter().collect(),
175            };
176            stack.extend(kids.into_iter().map(|c| (c, d + 1)));
177        }
178        deepest
179    }
180
181    pub fn span(&self) -> Span {
182        match self {
183            Expr::Const(_, s) | Expr::Param(_, s) | Expr::Name(_, s) => *s,
184            Expr::Control(_, s) => *s,
185            Expr::AmendIndex { span, .. } => *span,
186            Expr::Assign { span, .. }
187            | Expr::Monad { span, .. }
188            | Expr::Dyad { span, .. }
189            | Expr::PrintPass { span, .. }
190            | Expr::Fused { span, .. }
191            | Expr::Elided { span, .. }
192            | Expr::VerbDef { span, .. } => *span,
193        }
194    }
195
196    /// Widen (or move) the source this node points at. A parenthesised
197    /// expression uses it to take in its own brackets, so that a caret
198    /// under it underlines something balanced.
199    pub fn set_span(&mut self, to: Span) {
200        match self {
201            Expr::Const(_, s) | Expr::Param(_, s) | Expr::Name(_, s) => *s = to,
202            Expr::Control(_, s) => *s = to,
203            Expr::AmendIndex { span, .. } => *span = to,
204            Expr::Assign { span, .. }
205            | Expr::Monad { span, .. }
206            | Expr::Dyad { span, .. }
207            | Expr::PrintPass { span, .. }
208            | Expr::Fused { span, .. }
209            | Expr::Elided { span, .. }
210            | Expr::VerbDef { span, .. } => *span = to,
211        }
212    }
213
214    /// Sentences whose top level is an assignment, explicit output, or the
215    /// pass's record of what the program was yield no value to the
216    /// sequence.
217    fn is_silent(&self) -> bool {
218        matches!(
219            self,
220            Expr::Assign { .. }
221                | Expr::AmendIndex { .. }
222                | Expr::PrintPass { .. }
223                | Expr::Elided { .. }
224                | Expr::VerbDef { .. }
225        )
226    }
227}
228
229#[derive(Clone, Debug)]
230pub struct ParamSpec {
231    pub name: String,
232}
233
234/// A compiled program: immutable, reusable, holds no data bindings.
235#[derive(Clone, Debug)]
236pub struct Program {
237    pub stmts: Vec<Expr>,
238    pub params: Vec<ParamSpec>,
239    /// The source as the user would recognise it (interpolations shown as
240    /// `{name}`); all spans point into this string.
241    pub display_src: String,
242    pub agreement: Agreement,
243    pub fmt: FmtOpts,
244    /// The dialect this program was compiled under, resolved.
245    pub rules: Rules,
246}
247
248/// What an instrumented run saw at one node.
249#[derive(Clone, Debug)]
250pub(crate) struct Note {
251    pub shape: Vec<usize>,
252    pub dtype: crate::dtype::DType,
253    /// For a fused node: whether the kernel itself produced the value, and
254    /// the reason it declined when it did not.
255    pub kernel_ran: Option<bool>,
256    pub decline: Option<crate::fuse::Decline>,
257    /// For a fused node in a run that was given a device: where the
258    /// arithmetic happened.
259    pub placement: crate::device::Placement,
260}
261
262/// Notes from one run, keyed by the address of the node in the tree that
263/// ran. Explaining borrows the same `Program`, so the addresses still name
264/// the same nodes; nothing outside this crate ever sees them.
265pub(crate) type Trace = HashMap<usize, Note>;
266
267pub(crate) fn key(e: &Expr) -> usize {
268    std::ptr::from_ref(e) as usize
269}
270
271impl Program {
272    /// Execute with one value per parameter, in `params` order.
273    /// Returns None when the last sentence yields no value.
274    pub fn run(&self, args: &[Array], out: &mut dyn FnMut(&str)) -> Result<Option<Array>> {
275        self.exec(args, out, &mut None, None)
276    }
277
278    /// Execute with the fused kernels placed on `device`.
279    ///
280    /// Placement is not binding: the program, its data and its diagnostics
281    /// are the same whatever device is named here. What a device will not
282    /// take runs on the CPU, and `explain` says which and why.
283    pub fn run_on(
284        &self,
285        device: &crate::device::Device,
286        args: &[Array],
287        out: &mut dyn FnMut(&str),
288    ) -> Result<Option<Array>> {
289        self.exec(args, out, &mut None, Some(device))
290    }
291
292    /// Execute and record every node's result shape and dtype. The trace is
293    /// returned even when a sentence fails, so that a partial explanation
294    /// still shows what did run.
295    pub(crate) fn trace(
296        &self,
297        args: &[Array],
298        out: &mut dyn FnMut(&str),
299        device: Option<&crate::device::Device>,
300    ) -> (Result<Option<Array>>, Trace) {
301        let mut rec = Some(Trace::new());
302        let r = self.exec(args, out, &mut rec, device);
303        (r, rec.expect("the recorder stays in place"))
304    }
305
306    fn exec(
307        &self,
308        args: &[Array],
309        out: &mut dyn FnMut(&str),
310        rec: &mut Option<Trace>,
311        device: Option<&crate::device::Device>,
312    ) -> Result<Option<Array>> {
313        if args.len() != self.params.len() {
314            let names: Vec<&str> = self.params.iter().map(|p| p.name.as_str()).collect();
315            let wanted = if names.is_empty() {
316                "no arguments".to_string()
317            } else {
318                format!("one value for each of {}", names.join(", "))
319            };
320            return Err(Error::new(
321                ErrorKind::Value,
322                format!("this program takes {wanted}, and was given {}", args.len()),
323                None,
324            ));
325        }
326        let cfg = EvalCfg {
327            agreement: self.agreement,
328            fmt: self.fmt,
329            tol: self.rules.tol(),
330            rules: self.rules,
331        };
332        let mut env = Env::new(args.to_vec());
333        let mut ctx = Ctx { cfg, out, env: &mut env, device };
334        let mut last = None;
335        for stmt in &self.stmts {
336            // A control word cannot reach the top level in either language,
337            // so a loop signal here would have nowhere to go.
338            let (v, flow) = eval_stmt(stmt, &mut ctx, rec)?;
339            if flow != Flow::Normal {
340                return Err(Error::internal("a control signal escaped to the top level"));
341            }
342            last = if stmt.is_silent() { None } else { v };
343        }
344        Ok(last)
345    }
346
347    pub fn render_error(&self, e: &Error) -> String {
348        e.render(&self.display_src)
349    }
350
351    /// What this expression became, as text: one section per sentence,
352    /// giving the structure the frontend and the fusion pass produced.
353    ///
354    /// With one value per parameter (or none, for a program that takes
355    /// none) the program is also run, and every node is annotated with the
356    /// shape and dtype it produced — a fused node with whether its kernel
357    /// ran, and why not when it did not. The run is the ordinary one, so it
358    /// has the ordinary effects; output it makes is discarded here, and an
359    /// error stops the annotations and is reported at the end.
360    pub fn explain(&self, args: Option<&[Array]>) -> String {
361        crate::explain::explain(self, args, None)
362    }
363
364    /// [`Program::explain`], with the run placed on `device`: every fused
365    /// node then also says where its arithmetic happened, and why it was
366    /// not the device when it was not.
367    pub fn explain_on(
368        &self,
369        device: &crate::device::Device,
370        args: Option<&[Array]>,
371    ) -> String {
372        crate::explain::explain(self, args, Some(device))
373    }
374}
375
376/// Why a block stopped. `Normal` is falling off the end of it.
377#[derive(Clone, Copy, Debug, PartialEq, Eq)]
378pub(crate) enum Flow {
379    Normal,
380    Return,
381    Break,
382    Continue,
383    /// APL `→`: continue at this statement of the definition's body.
384    Goto(usize),
385}
386
387/// Run a block of sentences: the value is the last sentence's, and an
388/// assignment yields the value it assigned (the top level is the one place
389/// that discards it, and `Program::exec` applies that rule itself).
390pub(crate) fn run_block(
391    stmts: &[Expr],
392    last: Option<Array>,
393    ctx: &mut Ctx<'_>,
394    rec: &mut Option<Trace>,
395) -> Result<(Option<Array>, Flow)> {
396    let mut last = last;
397    for stmt in stmts {
398        let (v, flow) = eval_stmt(stmt, ctx, rec)?;
399        // `return.` and its relatives produce nothing of their own: the
400        // value in hand is what the definition hands back.
401        if let Some(v) = v {
402            last = Some(v);
403        }
404        if flow != Flow::Normal {
405            return Ok((last, flow));
406        }
407    }
408    Ok((last, Flow::Normal))
409}
410
411/// One sentence, control words included. The value of a control sentence is
412/// the value of the last sentence the branch it chose ran.
413fn eval_stmt(
414    e: &Expr,
415    ctx: &mut Ctx<'_>,
416    rec: &mut Option<Trace>,
417) -> Result<(Option<Array>, Flow)> {
418    let Expr::Control(c, span) = e else {
419        return Ok((Some(eval(e, ctx, rec)?), Flow::Normal));
420    };
421    let (v, flow) = eval_control(c, *span, ctx, rec)?;
422    // A branch that ran and produced nothing yields whatever the language
423    // gives an untaken branch: J's empty `i. 0 0`, and nothing at all in
424    // APL, where a function with no result is an error. A branch that left
425    // early yields nothing either way, so the value in hand survives.
426    let v = match (v, flow) {
427        (Some(v), _) => Some(v),
428        (None, Flow::Normal) => ctx.env.current_def().and_then(|d| d.empty.clone()),
429        (None, _) => None,
430    };
431    if let (Some(t), Some(v)) = (rec.as_mut(), v.as_ref()) {
432        t.insert(
433            key(e),
434            Note {
435                shape: v.shape.clone(),
436                dtype: v.dtype(),
437                kernel_ran: None,
438                decline: None,
439                placement: crate::device::Placement::Default,
440            },
441        );
442    }
443    Ok((v, flow))
444}
445
446/// The value of a branch that executed nothing: J's `i. 0 0`.
447pub(crate) fn empty_result() -> Array {
448    Array::new(vec![0, 0], Data::I64(Vec::new().into()))
449}
450
451/// J's truth: an empty condition is true, and otherwise the first atom
452/// decides. Characters count by their code point, as the reference does.
453fn is_true(a: &Array, span: Span) -> Result<bool> {
454    if a.count() == 0 {
455        return Ok(true);
456    }
457    match &a.data {
458        Data::I64(v) => Ok(v.as_slice()[0] != 0),
459        Data::F64(v) => Ok(v.as_slice()[0] != 0.0),
460        Data::Bool(v) => Ok(v.as_slice()[0] != 0),
461        Data::Char(v) => Ok(v.as_slice()[0] as u32 != 0),
462        Data::Complex(v) => Ok(v.as_slice()[0] != crate::complex::ZERO),
463        Data::Ext(v) => Ok(v.as_slice()[0] != crate::exact::Ext::default()),
464        Data::Rat(v) => Ok(!v.as_slice()[0].is_zero()),
465        Data::Box(_) => Err(Error::domain("a condition must be numeric, not boxed", span)),
466    }
467}
468
469fn eval_control(
470    c: &Control,
471    span: Span,
472    ctx: &mut Ctx<'_>,
473    rec: &mut Option<Trace>,
474) -> Result<(Option<Array>, Flow)> {
475    match c {
476        Control::Return => Ok((None, Flow::Return)),
477        // `→ e`: an empty target falls through, a line number of this
478        // definition jumps to it, and anything else leaves.
479        Control::Branch(target) => {
480            let to = eval(target, ctx, rec)?;
481            if to.count() == 0 {
482                return Ok((None, Flow::Normal));
483            }
484            let line = to
485                .to_i64_vec()
486                .and_then(|v| v.first().copied())
487                .ok_or_else(|| Error::domain("a branch target is a line number", span))?;
488            let lines = ctx.env.current_def().map_or(0, |d| d.body.len() as i64);
489            if line >= 1 && line <= lines {
490                return Ok((None, Flow::Goto(line as usize - 1)));
491            }
492            Ok((None, Flow::Return))
493        }
494        Control::Break => Ok((None, Flow::Break)),
495        Control::Continue => Ok((None, Flow::Continue)),
496        Control::If { arms, otherwise } => {
497            for arm in arms {
498                let test = arm.test.as_deref().unwrap_or(&[]);
499                let (t, flow) = run_block(test, None, ctx, rec)?;
500                if flow != Flow::Normal {
501                    return Ok((t, flow));
502                }
503                let taken = match &t {
504                    Some(v) => is_true(v, span)?,
505                    None => true,
506                };
507                if taken {
508                    return run_block(&arm.body, None, ctx, rec);
509                }
510            }
511            match otherwise {
512                Some(body) => run_block(body, None, ctx, rec),
513                None => Ok((None, Flow::Normal)),
514            }
515        }
516        Control::While { test, body, body_first, until } => {
517            let mut last = None;
518            let mut first = *body_first;
519            loop {
520                if !first {
521                    let (t, flow) = run_block(test, None, ctx, rec)?;
522                    if flow != Flow::Normal {
523                        return Ok((t, flow));
524                    }
525                    let mut go = match &t {
526                        Some(v) => is_true(v, span)?,
527                        None => false,
528                    };
529                    if *until {
530                        go = !go;
531                    }
532                    if !go {
533                        return Ok((last, Flow::Normal));
534                    }
535                }
536                first = false;
537                let (v, flow) = run_block(body, last, ctx, rec)?;
538                last = v;
539                match flow {
540                    Flow::Normal | Flow::Continue => {}
541                    Flow::Break => return Ok((last, Flow::Normal)),
542                    // A branch out of a loop leaves the loop, and the
543                    // definition's own statement list takes it from there.
544                    other => return Ok((last, other)),
545                }
546            }
547        }
548        Control::For { name, source, body } => {
549            let src = eval(source, ctx, rec)?;
550            let n = if src.rank() == 0 { 1 } else { src.shape[0] };
551            let mut last = None;
552            for i in 0..n {
553                if let Some(name) = name {
554                    let item = if src.rank() == 0 { src.clone() } else { src.item(i) };
555                    ctx.env.assign(name.clone(), item, Scope::Local);
556                    ctx.env.assign(
557                        format!("{name}_index"),
558                        Array::scalar_i64(i as i64),
559                        Scope::Local,
560                    );
561                }
562                let (v, flow) = run_block(body, last, ctx, rec)?;
563                last = v;
564                match flow {
565                    Flow::Normal | Flow::Continue => {}
566                    Flow::Break => return Ok((last, Flow::Normal)),
567                    // A branch out of a loop leaves the loop, and the
568                    // definition's own statement list takes it from there.
569                    other => return Ok((last, other)),
570                }
571            }
572            Ok((last, Flow::Normal))
573        }
574        Control::Select { subject, cases } => {
575            let subject = eval(subject, ctx, rec)?;
576            let tol = ctx.cfg.tol;
577            let mut running = false;
578            let mut last = None;
579            for case in cases {
580                if !running {
581                    match &case.test {
582                        None => running = true,
583                        Some(test) => {
584                            let (t, flow) = run_block(test, None, ctx, rec)?;
585                            if flow != Flow::Normal {
586                                return Ok((t, flow));
587                            }
588                            // The reference compares with match (`-:`), not
589                            // membership: `case. 1 2` takes the list 1 2.
590                            running = t.is_some_and(|v| arrays_match(&subject, &v, tol));
591                        }
592                    }
593                }
594                if running {
595                    let (v, flow) = run_block(&case.body, last, ctx, rec)?;
596                    last = v;
597                    if flow != Flow::Normal {
598                        return Ok((last, flow));
599                    }
600                    if !case.fall_through {
601                        return Ok((last, Flow::Normal));
602                    }
603                    // `fcase.` runs the next body without testing it.
604                    running = true;
605                }
606            }
607            Ok((last, Flow::Normal))
608        }
609        Control::Try { body, catch } => {
610            // The catch block answers for the languages' own errors. A gap
611            // in libjay is not one of them: swallowing a "not supported
612            // yet" would turn a promise into a wrong answer.
613            match run_block(body, None, ctx, rec) {
614                Ok(r) => Ok(r),
615                Err(e) if matches!(e.kind, ErrorKind::NotYet | ErrorKind::Internal) => Err(e),
616                Err(_) => run_block(catch, None, ctx, rec),
617            }
618        }
619    }
620}
621
622/// Apply an explicit definition. `x` is None for a monadic application.
623pub(crate) fn call_explicit(
624    def: &Arc<ExplicitDef>,
625    x: Option<&Array>,
626    y: &Array,
627    ctx: &mut Ctx<'_>,
628    span: Span,
629) -> Result<Array> {
630    if x.is_some() && def.left.is_none() {
631        return Err(Error::new(
632            ErrorKind::Domain,
633            format!("{} has no dyadic definition", def.name),
634            Some(span),
635        ));
636    }
637    if x.is_none() && def.dyad_only {
638        return Err(Error::new(
639            ErrorKind::Domain,
640            format!(
641                "{} has no monadic definition: it names {}",
642                def.name,
643                def.left.as_deref().unwrap_or("a left argument")
644            ),
645            Some(span),
646        ));
647    }
648    let mut frame: HashMap<String, Array> = HashMap::new();
649    frame.insert(def.right.clone(), y.clone());
650    if let (Some(name), Some(v)) = (&def.left, x) {
651        frame.insert(name.clone(), v.clone());
652    }
653    // A label's value is its line number, which is what `→` takes.
654    for (label, at) in &def.labels {
655        frame.insert(label.clone(), Array::scalar_i64(*at as i64 + 1));
656    }
657    ctx.env.enter(frame, Arc::clone(def), span)?;
658    let mut rec = None;
659    let out = run_body(&def.body, ctx, &mut rec);
660    let frame = ctx.env.leave();
661    let value = out?;
662    // An APL `∇`-definition names its result; the body's own value is not
663    // it, and a definition that never assigned the name has no result.
664    if let Some(name) = &def.result {
665        return frame.get(name).cloned().ok_or_else(|| {
666            Error::new(
667                ErrorKind::Value,
668                format!("{} did not set its result {name}", def.name),
669                Some(span),
670            )
671        });
672    }
673    match value {
674        Some(v) => Ok(v),
675        None => def.empty.clone().ok_or_else(|| {
676            Error::new(
677                ErrorKind::Value,
678                format!("{} produced no result", def.name),
679                Some(span),
680            )
681        }),
682    }
683}
684
685/// How many statements a branching definition may run before libjay stops
686/// it. A `→` loop has no other bound, and an unbounded one would hang.
687const BRANCH_LIMIT: usize = 1 << 22;
688
689/// A definition's body, statement by statement, with `→` free to move the
690/// place it runs from. The value is the last statement that produced one.
691fn run_body(
692    stmts: &[Expr],
693    ctx: &mut Ctx<'_>,
694    rec: &mut Option<Trace>,
695) -> Result<Option<Array>> {
696    let mut last = None;
697    let mut at = 0usize;
698    let mut steps = 0usize;
699    while at < stmts.len() {
700        steps += 1;
701        if steps > BRANCH_LIMIT {
702            return Err(Error::new(
703                ErrorKind::Domain,
704                format!("a definition branched more than {BRANCH_LIMIT} times"),
705                Some(stmts[at].span()),
706            )
707            .note("a loop written with → needs a branch that leaves it"));
708        }
709        let (v, flow) = eval_stmt(&stmts[at], ctx, rec)?;
710        if let Some(v) = v {
711            last = Some(v);
712        }
713        match flow {
714            Flow::Normal => at += 1,
715            Flow::Goto(to) => at = to,
716            _ => break,
717        }
718    }
719    Ok(last)
720}
721
722/// A noun expression's value where the whole of it can be settled now:
723/// constants combined by pure verbs, with no name, no bound parameter and
724/// no control flow anywhere in it. Modifiers that capture a noun operand
725/// use this, so a written-out `(<a:;1)}` is as good as a literal.
726pub(crate) fn fold_const(e: &Expr, cfg: EvalCfg) -> Option<Array> {
727    fn closed(e: &Expr) -> bool {
728        match e {
729            Expr::Const(..) => true,
730            Expr::Monad { verb, y, .. } => verb.is_pure() && closed(y),
731            Expr::Dyad { verb, x, y, .. } => verb.is_pure() && closed(x) && closed(y),
732            _ => false,
733        }
734    }
735    if !closed(e) {
736        return None;
737    }
738    cfg.pure(|ctx| eval(e, ctx, &mut None).ok())
739}
740
741fn eval(e: &Expr, ctx: &mut Ctx<'_>, rec: &mut Option<Trace>) -> Result<Array> {
742    // The walk is recursive, so a deeply nested sentence would run out of
743    // stack; the ceiling turns that into a diagnostic.
744    let _depth = crate::verb::Nesting::enter(e.span())?;
745    let v = eval_node(e, ctx, rec)?;
746    if let Some(t) = rec.as_mut() {
747        // A fused node has already left what it knows about its kernel.
748        let (kernel_ran, decline, placement) = t.get(&key(e)).map_or(
749            (None, None, crate::device::Placement::Default),
750            |n| (n.kernel_ran, n.decline, n.placement.clone()),
751        );
752        t.insert(
753            key(e),
754            Note { shape: v.shape.clone(), dtype: v.dtype(), kernel_ran, decline, placement },
755        );
756    }
757    Ok(v)
758}
759
760fn eval_node(e: &Expr, ctx: &mut Ctx<'_>, rec: &mut Option<Trace>) -> Result<Array> {
761    match e {
762        Expr::Const(a, _) => Ok(a.clone()),
763        Expr::Param(i, _) => ctx.env.arg(*i),
764        Expr::Name(n, span) => ctx.env.get(n).ok_or_else(|| {
765            Error::new(ErrorKind::Value, format!("undefined name: {n}"), Some(*span))
766        }),
767        Expr::Assign { name, value, scope, .. } => {
768            let v = eval(value, ctx, rec)?;
769            ctx.env.assign(name.clone(), v.clone(), *scope);
770            Ok(v)
771        }
772        Expr::AmendIndex { name, slots, value, origin, scope, span } => {
773            let base = ctx.env.get(name).ok_or_else(|| {
774                Error::new(ErrorKind::Value, format!("undefined name: {name}"), Some(*span))
775            })?;
776            // The sentence reads right to left, so the value comes first.
777            let v = eval(value, ctx, rec)?;
778            let mut idx = Vec::with_capacity(slots.len());
779            for slot in slots {
780                idx.push(match slot {
781                    Some(e) => Some(eval(e, ctx, rec)?),
782                    None => None,
783                });
784            }
785            let out = crate::verb::amend_at(&base, &idx, &v, *origin, *span)?;
786            ctx.env.assign(name.clone(), out.clone(), *scope);
787            Ok(out)
788        }
789        // A control sentence is run by `eval_stmt`, which is the only place
790        // its signal has anywhere to go.
791        Expr::Control(..) => {
792            Err(Error::internal("a control sentence appeared in expression position"))
793        }
794        Expr::Monad { verb, y, span } => {
795            let vy = eval(y, ctx, rec)?;
796            verb.monad(&vy, ctx, *span)
797        }
798        Expr::Dyad { verb, x, y, span } => {
799            // The right argument evaluates first: both languages read
800            // sentences right to left, and inline assignments rely on it.
801            let vy = eval(y, ctx, rec)?;
802            let vx = eval(x, ctx, rec)?;
803            verb.dyad(&vx, &vy, ctx, *span)
804        }
805        Expr::PrintPass { value, .. } => {
806            let v = eval(value, ctx, rec)?;
807            let text = format_array(&v, &ctx.cfg.fmt);
808            (ctx.out)(&text);
809            (ctx.out)("\n");
810            Ok(v)
811        }
812        Expr::Fused { kernel, inputs, orig, .. } => {
813            let mut vals = Vec::with_capacity(inputs.len());
814            for e in inputs {
815                vals.push(eval(e, ctx, rec)?);
816            }
817            let (ran, placement) = crate::fuse::eval_on(ctx.device, kernel, &vals);
818            if let Some(t) = rec.as_mut() {
819                let decline =
820                    if ran.is_none() { crate::fuse::decline_reason(kernel, &vals) } else { None };
821                // Shape and dtype arrive from the wrapper above; only the
822                // kernel's own story is recorded here.
823                t.insert(
824                    key(e),
825                    Note {
826                        shape: Vec::new(),
827                        dtype: crate::dtype::DType::I64,
828                        kernel_ran: Some(ran.is_some()),
829                        decline,
830                        placement,
831                    },
832                );
833            }
834            match ran {
835                Some(a) => Ok(a),
836                // The kernel does not cover this data. The chain it came
837                // from does, including whatever error it raises; it runs
838                // over the values just computed, not over the leaves again.
839                None => {
840                    let tree = crate::fuse::fallback_tree(kernel, orig, &vals);
841                    // The fallback tree is temporary, so its nodes are not
842                    // ones an explanation can name: it runs unrecorded.
843                    let v = eval(&tree, ctx, &mut None)?;
844                    Ok(crate::fuse::fallback_finish(kernel, v))
845                }
846            }
847        }
848        // Naming a verb records it so that a definition can call itself by
849        // name; the sentence is silent, so the value is never read.
850        Expr::VerbDef { name, verb, .. } => {
851            ctx.env.define(name.clone(), verb.clone());
852            Ok(Array::scalar_i64(0))
853        }
854        // A record of what the program was: a silent sentence whose value
855        // is never read.
856        Expr::Elided { .. } => Ok(Array::scalar_i64(0)),
857    }
858}