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