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