Skip to main content

jay/
gerund.rs

1//! J's atomic representation, which is what a gerund is made of.
2//!
3//! `` u`v `` is not a parse-time object in J: it is boxed data, one box per
4//! tied entity, and each box holds that entity's atomic representation. A
5//! primitive is its own spelling as a character vector; a noun is the pair
6//! `('0'; <value)`; a train is `('2'; <parts)` or `('3'; <parts)`; and
7//! anything a modifier derived is `(spelling; <operands)`. Everything is
8//! therefore ordinary data, which is what lets a gerund be assigned,
9//! computed and displayed like any other noun.
10
11use crate::array::{Array, Data};
12use crate::verb::{Enclose, Power, Verb, WindowKind, RANK_INF};
13
14/// One atomic representation.
15#[derive(Clone, Debug, PartialEq)]
16pub enum Ar {
17    /// A primitive, spelled as it is written.
18    Prim(String),
19    /// A noun operand, which stands for itself.
20    Noun(Array),
21    /// What a modifier derived, by the modifier's spelling and its operands.
22    Derived(String, Vec<Ar>),
23    /// A hook (two parts) or a fork (three).
24    Train(Vec<Ar>),
25}
26
27fn chars(s: &str) -> Array {
28    Array::from_chars(s.chars().collect())
29}
30
31fn boxes(items: Vec<Array>) -> Array {
32    Array::new(vec![items.len()], Data::Box(items.into()))
33}
34
35/// The two-box pair every derived representation takes.
36fn pair(head: Array, body: Array) -> Array {
37    boxes(vec![head, body])
38}
39
40impl Ar {
41    /// The representation as the boxed data J spells it with.
42    pub fn to_array(&self) -> Array {
43        match self {
44            Ar::Prim(s) => chars(s),
45            Ar::Noun(a) => pair(chars("0"), a.clone()),
46            Ar::Derived(s, ops) => {
47                pair(chars(s), boxes(ops.iter().map(Ar::to_array).collect()))
48            }
49            Ar::Train(ops) => {
50                let tag = if ops.len() == 2 { "2" } else { "3" };
51                pair(chars(tag), boxes(ops.iter().map(Ar::to_array).collect()))
52            }
53        }
54    }
55
56    /// The representation read back out of boxed data, or `None` where the
57    /// data is not one.
58    pub fn from_array(a: &Array) -> Option<Ar> {
59        if let Some(text) = text_of(a) {
60            return Some(Ar::Prim(text));
61        }
62        let items = a.as_boxes()?;
63        if a.rank() != 1 || items.len() != 2 {
64            return None;
65        }
66        let head = text_of(&items[0])?;
67        if head == "0" {
68            return Some(Ar::Noun(items[1].clone()));
69        }
70        let parts: Option<Vec<Ar>> = items[1].as_boxes()?.iter().map(Ar::from_array).collect();
71        let parts = parts?;
72        match head.as_str() {
73            "2" | "3" => Some(Ar::Train(parts)),
74            _ => Some(Ar::Derived(head, parts)),
75        }
76    }
77}
78
79/// A character vector or atom as a string; `None` for anything else.
80pub fn text_of(a: &Array) -> Option<String> {
81    if a.rank() > 1 {
82        return None;
83    }
84    match a.row_major_data() {
85        Data::Char(v) => Some(v.as_slice().iter().collect()),
86        _ => None,
87    }
88}
89
90/// The gerund `` u`v `` builds: one box per representation.
91pub fn gerund_array(items: &[Ar]) -> Array {
92    boxes(items.iter().map(Ar::to_array).collect())
93}
94
95/// A rank specification as the noun `u"n` was given.
96fn rank_noun(r: &[i64; 3]) -> Array {
97    let one = |v: i64| if v == RANK_INF { f64::INFINITY } else { v as f64 };
98    if r[0] == r[1] && r[1] == r[2] {
99        return Array::new(vec![], Data::F64(vec![one(r[0])].into()));
100    }
101    Array::from_f64(vec![one(r[0]), one(r[1]), one(r[2])])
102}
103
104fn power_noun(p: &Power) -> Option<Array> {
105    Some(match p {
106        Power::Times(n) => Array::scalar_i64(*n as i64),
107        Power::Converge => Array::scalar_f64(f64::INFINITY),
108        Power::Each(ns) => Array::from_i64(ns.iter().map(|&n| n as i64).collect()),
109        // `u^:a:` is the ace, which is what `` ` `` would have to write out.
110        Power::ConvergeTrace => Array::boxed(Array::empty(crate::dtype::DType::I64)),
111    })
112}
113
114/// The atomic representation of a verb, or `None` where libjay has no
115/// spelling to give it — a verb from the APL frontend, or one whose parts
116/// the tree no longer names.
117pub fn verb_ar(v: &Verb) -> Option<Ar> {
118    let der = |s: &str, ops: Vec<Ar>| Some(Ar::Derived(s.to_string(), ops));
119    match v {
120        // `m b.` is a truth table with no spelling of its own left in the
121        // tree, so it is the one primitive that cannot be written back out.
122        Verb::Prim(p) if p.name == "b." => None,
123        Verb::Prim(p) => Some(Ar::Prim(p.name.to_string())),
124        Verb::Rank(inner, r) => rank_ar(inner, r),
125        Verb::Reduce(u) => der("/", vec![verb_ar(u)?]),
126        Verb::Windowed(u, WindowKind::Prefix) => der("\\", vec![verb_ar(u)?]),
127        Verb::Windowed(u, WindowKind::Suffix) => der("\\.", vec![verb_ar(u)?]),
128        Verb::Windowed(_, WindowKind::Scan) => None,
129        Verb::Commute(u) => der("~", vec![verb_ar(u)?]),
130        Verb::PowerN(u, p) => der("^:", vec![verb_ar(u)?, Ar::Noun(power_noun(p)?)]),
131        Verb::PowerV(u, w) => der("^:", vec![verb_ar(u)?, verb_ar(w)?]),
132        Verb::Fork(f, g, h) => Some(Ar::Train(vec![verb_ar(f)?, verb_ar(g)?, verb_ar(h)?])),
133        Verb::NounFork(n, g, h) => {
134            Some(Ar::Train(vec![Ar::Noun(n.clone()), verb_ar(g)?, verb_ar(h)?]))
135        }
136        Verb::Hook(f, g) => Some(Ar::Train(vec![verb_ar(f)?, verb_ar(g)?])),
137        Verb::Atop(f, g) => match under_ar(f, g, "&.:") {
138            Some(ar) => Some(ar),
139            None => der("@:", vec![verb_ar(f)?, verb_ar(g)?]),
140        },
141        Verb::Compose(f, g) => der("&:", vec![verb_ar(f)?, verb_ar(g)?]),
142        Verb::BondLeft(m, u) => der("&", vec![Ar::Noun(m.clone()), verb_ar(u)?]),
143        Verb::BondRight(u, n) => der("&", vec![verb_ar(u)?, Ar::Noun(n.clone())]),
144        Verb::Each(u, Enclose::Always) => {
145            der("&.", vec![verb_ar(u)?, Ar::Prim(">".to_string())])
146        }
147        Verb::Each(_, Enclose::ExceptSimpleScalar) => None,
148        Verb::Fit(u, n) => der("!.", vec![verb_ar(u)?, Ar::Noun(Array::scalar_f64(*n))]),
149        Verb::Amend(m) => der("}", vec![Ar::Noun(m.clone())]),
150        Verb::AmendVerb(u) => der("}", vec![verb_ar(u)?]),
151        Verb::Memo(u, _) => der("M.", vec![verb_ar(u)?]),
152        Verb::Level { u, level, spread } => der(
153            if *spread { "S:" } else { "L:" },
154            vec![verb_ar(u)?, Ar::Noun(Array::scalar_i64(*level))],
155        ),
156        Verb::Characteristics(u) => der("b.", vec![verb_ar(u)?]),
157        Verb::Key(u) => der("/.", vec![verb_ar(u)?]),
158        Verb::Cut(u, n) => der(";.", vec![verb_ar(u)?, Ar::Noun(Array::scalar_i64(*n))]),
159        Verb::Adverse(u, w) => der("::", vec![verb_ar(u)?, verb_ar(w)?]),
160        Verb::WithObverse(u, w) => der(":.", vec![verb_ar(u)?, verb_ar(w)?]),
161        Verb::Agenda(vs, w) => {
162            let items: Option<Vec<Ar>> = vs.iter().map(verb_ar).collect();
163            der("@.", vec![Ar::Noun(gerund_array(&items?)), verb_ar(w)?])
164        }
165        Verb::Evoke(vs, n) => {
166            let items: Option<Vec<Ar>> = vs.iter().map(verb_ar).collect();
167            der(
168                "`:",
169                vec![Ar::Noun(gerund_array(&items?)), Ar::Noun(Array::scalar_i64(*n))],
170            )
171        }
172        Verb::SelfRef => Some(Ar::Prim("$:".to_string())),
173        _ => None,
174    }
175}
176
177/// `u"n`, and the three conjunctions J spells by applying at an operand's
178/// own rank: `u@v`, `u&v` and `u&.v` are each a rank around what `@:`,
179/// `&:` and `&.:` derive, so the rank they set is what tells them apart.
180fn rank_ar(inner: &Verb, r: &[i64; 3]) -> Option<Ar> {
181    let der = |s: &str, ops: Vec<Ar>| Some(Ar::Derived(s.to_string(), ops));
182    // `u&.v` sets v's MONADIC rank around the same tree `&.:` builds, which
183    // is what separates it from `u@v` — that one sets all three of g's.
184    if let Verb::Atop(f, g) = inner
185        && matches!(&**g, Verb::Compose(_, u) if *r == [u.ranks()[0]; 3])
186        && let Some(ar) = under_ar(f, g, "&.")
187    {
188        return Some(ar);
189    }
190    match inner {
191        Verb::Atop(f, g) if *r == g.ranks() => der("@", vec![verb_ar(f)?, verb_ar(g)?]),
192        Verb::Compose(f, g) if *r == [g.ranks()[0]; 3] => {
193            der("&", vec![verb_ar(f)?, verb_ar(g)?])
194        }
195        Verb::BondLeft(m, g) if *r == [g.ranks()[2]; 3] => {
196            der("&", vec![Ar::Noun(m.clone()), verb_ar(g)?])
197        }
198        Verb::BondRight(g, n) if *r == [g.ranks()[1]; 3] => {
199            der("&", vec![verb_ar(g)?, Ar::Noun(n.clone())])
200        }
201        _ => der("\"", vec![verb_ar(inner)?, Ar::Noun(rank_noun(r))]),
202    }
203}
204
205/// `u&.v` and `u&.:v` are built as `v^:_1 @: (u &: v)`; this recognises
206/// that shape and gives the spelling back. The left part must really be v's
207/// obverse, which is what keeps an ordinary `f@:(g&:h)` out.
208fn under_ar(f: &Verb, g: &Verb, spelling: &str) -> Option<Ar> {
209    let Verb::Compose(inner, under) = g else { return None };
210    let obverse = crate::frontend::j::obverse_of(under, crate::error::Span::new(0, 0)).ok()?;
211    if obverse.name() != f.name() {
212        return None;
213    }
214    Some(Ar::Derived(spelling.to_string(), vec![verb_ar(inner)?, verb_ar(under)?]))
215}