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