sourcework/xetal-libraries/libs/Equations/src/Equations.xtlm
1⍝│ Equations: its macro library -- e_q<, t_ex< and b_oth<: an equation as
2⍝│ printed in a paper, to its value, its LaTeX, or both.
3⍝│ Imported with the functions, under one alias: "e:" u_se< "Equations".
4⍝│ A macro is a function from the source text written left and right of
5⍝│ its call to the source that replaces the call, run before the program
6⍝│ is compiled (X_eTaL MC10). Names with m: are macros; those under h: are
7⍝│ private to this file.
8
9"t:" u̲se< "Strings"
10"pa:" u̲se< "Parsing"
11
12⍝━━ The macros
13
14⍝│ The value of an equation written as a paper prints it. The left names
15⍝│ the result, its free indices and their extents ("Y[o,h,w] | O Ho Wo",
16⍝│ or "L[] | " for a scalar). The right is an expression over the free
17⍝│ indices in which a parenthesized sum is a term:
18⍝│ "(sum j:=0..M-1 | W[i,j] * x[j]) + b[i]"
19⍝│ A sum is an operator word (sum, prod, max, min), an index, := and an
20⍝│ inclusive range low..high, then | and its body; several, separated by
21⍝│ commas, nest outermost first. A right side that is one bare sum needs
22⍝│ no parentheses: "sum c:=0..C-1, sum r:=0..Kh-1 | X[c,h+r] * W[c,r]".
23⍝│ The word integral binds a continuous variable over Float bounds and
24⍝│ takes the value of its body by a quadrature rule written after by:
25⍝│ "integral tau:=0..1 by simpson 64 | exp(tau)" (trapezoid or simpson,
26⍝│ then the number of steps; without by, trapezoid 64; simpson needs an
27⍝│ even number). The variable is the grid of n + 1 points a, a + h, ...,
28⍝│ b; a bound of inf is refused (write a truncation and show the tail is
29⍝│ small, or change variables). Trapezoid is exact on lines, simpson on
30⍝│ cubics; at a jump of the body the sample takes the body's value at
31⍝│ that point as written.
32⍝│ Expressions follow math precedence: ^ binds tightest (right to left),
33⍝│ then * and /, then + and - (left to right); parentheses group; the
34⍝│ functions are exp(x), log(x), sqrt(x), abs(x), sin(x), cos(x),
35⍝│ max(a, b) and min(a, b); A[e1,e2] reads one element of the caller's array A, one
36⍝│ index per axis, from 0. Every reading rule uses all of its text or
37⍝│ refuses the call, and what was read is written back and compared
38⍝│ with the text at every call, so nothing written is ever dropped.
39⍝│ Both sides are notations (X_eTaL MC32): where a program is drawn
40⍝│ (the pages, the live demo, xetal render --html) the call's two
41⍝│ strings are shown as one math box, what e:t_ex< gives for them,
42⍝│ after the macro's name; the value and the text are unchanged.
43⍝│ takes: notation notation
44⍝│ draws: t_ex<
45⍝│ uses: parsed
46ᵐe̲q< ← { lhs rhs → (ʰf̲ree lhs) ʰv̲alue ʰc̲hecked rhs }
47
48⍝│ The LaTeX of the same call, a string: the result as written (L for a
49⍝│ scalar, Y_{o,h,w} for an array), then = and the expression, with
50⍝│ \sum \prod \max \min, \int_{a}^{b} ... \, d\tau for an integral (a
51⍝│ Greek letter's name as its command), \frac for a quotient, \sqrt \exp
52⍝│ \log \sin \cos, and the brackets as subscripts.
53⍝│ takes: text text
54⍝│ uses: parsed
55ᵐt̲ex< ← { lhs rhs → "\"" c̲at ((0 ʰt̲ex lhs)_ ʰc̲hecked rhs) c̲at "\"" }
56
57⍝│ The same LaTeX with each symbol the caller supplies marked for a
58⍝│ page: the result, every array reference with its subscripts and every
59⍝│ named scalar (in a body or a range bound) wrapped in KaTeX's
60⍝│ \htmlClass{sym sym-NAME}{...}, NAME as written (a Greek letter by its
61⍝│ word: sym-tau). Indices, an integral's variable, prev, functions and
62⍝│ numbers are not wrapped (the classes idx and idx-NAME are kept for
63⍝│ indices). Everything else is e:t_ex<'s output, unchanged. KaTeX draws
64⍝│ \htmlClass only with trust allowing it and strict letting the
65⍝│ htmlExtension through.
66⍝│ takes: text text
67⍝│ uses: parsed
68ᵐt̲exSymbols< ← { lhs rhs → "\"" c̲at ((1 ʰt̲ex lhs)_ ʰc̲hecked rhs) c̲at "\"" }
69
70⍝│ Both from one call: the pair (value, LaTeX); drawn as e:e_q< is.
71⍝│ takes: notation notation
72⍝│ draws: t_ex<
73⍝│ uses: parsed
74ᵐb̲oth< ← { lhs rhs → "(" c̲at ((ʰf̲ree lhs) ʰv̲alue ʰc̲hecked rhs) c̲at ", \"" c̲at ((0 ʰt̲ex lhs)_ ʰc̲hecked rhs) c̲at "\")" }
75
76⍝│ Discrete 1-D convolution and cross-correlation, written as the
77⍝│ sums they are (X_eTaL Saga 42 decision 5: library macros over sum,
78⍝│ not words of the grammar). The left names the result, its index and
79⍝│ the boundary mode, "y[n] | full"; the right names the signal and the
80⍝│ kernel, two of the caller's vectors, "x h". The modes: full gives
81⍝│ L + M - 1 values (every overlap, the signal padded with M - 1 zeros
82⍝│ at each end), same the L centered on full (as numpy centers them),
83⍝│ valid the L - M + 1 complete overlaps (a kernel longer than the
84⍝│ signal stops the program when it runs); no mode is implicit.
85⍝│ Convolution is y[n] = sum over m of x[n - m] h[m], the kernel
86⍝│ reversed as it slides; correlation y[n] = sum over m of x[n + m]
87⍝│ h[m], not reversed, so correlation is convolution with the kernel
88⍝│ reversed. xetal expand shows the sum each call becomes. Both sides
89⍝│ are notations (MC32), drawn by e:c_onvolveTex<.
90⍝│ takes: notation notation
91⍝│ draws: c_onvolveTex<
92⍝│ uses: parsed
93ᵐc̲onvolve< ← { lhs rhs → "convolve" ʰs̲lide (lhs, rhs) }
94
95⍝│ Cross-correlation, the same call shape (see c_onvolve<); drawn by
96⍝│ e:c_orrelateTex<.
97⍝│ takes: notation notation
98⍝│ draws: c_orrelateTex<
99⍝│ uses: parsed
100ᵐc̲orrelate< ← { lhs rhs → "correlate" ʰs̲lide (lhs, rhs) }
101
102⍝│ The LaTeX of a convolution call, a string: the textbook sum,
103⍝│ y_{n} = \sum_{m} x_{n-m} \, h_{m}, with the mode after it in words.
104⍝│ takes: text text
105⍝│ uses: parsed
106ᵐc̲onvolveTex< ← { lhs rhs → "convolve" ʰs̲lideTex (lhs, rhs) }
107
108⍝│ The LaTeX of a correlation call (x_{n+m}, the kernel not reversed).
109⍝│ takes: text text
110⍝│ uses: parsed
111ᵐc̲orrelateTex< ← { lhs rhs → "correlate" ʰs̲lideTex (lhs, rhs) }
112
113⍝━━ Convolution and correlation
114
115⍝│ The boundary modes.
116modes ← "full" "same" "valid"
117
118⍝│ The expansion of a convolution or correlation call: a block that
119⍝│ measures the signal (L) and the kernel (M), pads the signal with
120⍝│ M - 1 zeros of its own type at each end (full and same), checks the
121⍝│ lengths (valid), then the equation over the free index, computed
122⍝│ as any equation is.
123ʰs̲lide ← { op (lhs, rhs) →
124 (y, n, mode) ← ʰs̲lideLeft lhs
125 (x, h) ← ʰs̲lideRight rhs
126 (gL, gM, gC, gP) ← (⎕G̲ENSYM "L", ⎕G̲ENSYM "M", ⎕G̲ENSYM "c", ⎕G̲ENSYM "p")
127 m ← n ʰo̲therIndex ("m", "j", "q")
128 lines ← " " c̲at gL c̲at " := t_ally " c̲at x c̲at "\n " c̲at gM c̲at " := t_ally " c̲at h c̲at "\n " c̲at gC c̲at " := (" c̲at gM c̲at " - 1) d_iv 2\n " c̲at gP c̲at " := ((" c̲at gM c̲at " - 1) r_eshape 0 * 1 t_ake " c̲at x c̲at ") c_at " c̲at x c̲at " c_at (" c̲at gM c̲at " - 1) r_eshape 0 * 1 t_ake " c̲at x c̲at "\n"
129 check ← ("valid" m̲atch mode) ʰp̲ickText (" _ := { @ -> " c̲at gM c̲at " <= " c̲at gL c̲at " ? @; \"valid-mode\" []S_IGNAL \"valid: the kernel (\" c_at (f_ormat " c̲at gM c̲at ") c_at \") is longer than the signal (\" c_at (f_ormat " c̲at gL c̲at ") c_at \")\" } @\n", "")
130 extent ← ("full" m̲atch mode) ʰp̲ickText (gL c̲at "+" c̲at gM c̲at "-1", ("same" m̲atch mode) ʰp̲ickText (gL, gL c̲at "-" c̲at gM c̲at "+1"))
131 signal ← ("valid" m̲atch mode) ʰp̲ickText (x, gP)
132 shift ← ("full" m̲atch mode) ʰp̲ickText (gM c̲at "-1", ("same" m̲atch mode) ʰp̲ickText (gM c̲at "-1+" c̲at gC, ""))
133 at ← ("convolve" m̲atch op) ʰp̲ickText (n c̲at (ʰp̲lus ("valid" m̲atch mode) ʰp̲ickText (gM c̲at "-1", shift)) c̲at "-" c̲at m, n c̲at (ʰp̲lus ("valid" m̲atch mode) ʰp̲ickText ("", ("full" m̲atch mode) ʰp̲ickText ("", gC))) c̲at "+" c̲at m)
134 eq ← "sum " c̲at m c̲at ":=0.." c̲at gM c̲at "-1 | " c̲at signal c̲at "[" c̲at at c̲at "] * " c̲at h c̲at "[" c̲at m c̲at "]"
135 "({ @ ->\n" c̲at lines c̲at check c̲at " " c̲at ((ʰf̲ree y c̲at "[" c̲at n c̲at "] | " c̲at extent) ʰv̲alue ʰc̲hecked eq) c̲at "\n} @)"
136}
137
138⍝│ The LaTeX of a convolution or correlation call as a string literal:
139⍝│ the result and its index, = , the sum over the kernel's index of the
140⍝│ signal at n - m (convolution) or n + m (correlation) times the
141⍝│ kernel at m, then the mode in words; the sides read as the call is.
142ʰs̲lideTex ← { op (lhs, rhs) →
143 (y, n, mode) ← ʰs̲lideLeft lhs
144 (x, h) ← ʰs̲lideRight rhs
145 m ← n ʰo̲therIndex ("m", "j", "q")
146 sign ← ("convolve" m̲atch op) ʰp̲ickText (" - ", " + ")
147 "\"" c̲at y c̲at "_{" c̲at n c̲at "} = \\\\sum_{" c̲at m c̲at "} " c̲at x c̲at "_{" c̲at n c̲at sign c̲at m c̲at "} \\\\, " c̲at h c̲at "_{" c̲at m c̲at "} \\\\quad \\\\text{(" c̲at mode c̲at ")}\""
148}
149
150⍝│ "+" and t, or "" for an empty t.
151ʰp̲lus ← { t →
152 0 = t̲ally t ? ""
153 "+" c̲at t
154}
155
156⍝│ The left side "y[n] | mode" as (result, index, mode): one index, a
157⍝│ mode the library has.
158ʰs̲lideLeft ← { lhs →
159 l ← "|" ᵖᵃs̲plit lhs
160 _ ← (2 = t̲ally l) ᵖᵃr̲equire ("bad-convolution left", "the result, its index and the mode: \"y[n] | full\"")
161 (y, ix) ← "bad-convolution left" ᵖᵃr̲eference d̲isclose f̲irst l
162 _ ← (1 = t̲ally ix) ᵖᵃr̲equire ("bad-convolution left", "one index: \"y[n] | full\", not " c̲at lhs)
163 mode ← ᵗt̲rim d̲isclose 2 s̲elect l
164 _ ← (0 < t̲ally mode) ᵖᵃr̲equire ("bad-convolution left", "the mode is not implicit: write full, same or valid after |")
165 _ ← ('∨ r̲/ 0 + '{ w → (d̲isclose w) m̲atch mode } e̲ach modes) ᵖᵃr̲equire ("bad-convolution left", "the mode is full, same or valid, not " c̲at mode)
166 (y, d̲isclose f̲irst ix, mode)
167}
168
169⍝│ The right side "x h" as (signal, kernel): two names.
170ʰs̲lideRight ← { rhs →
171 ws ← ᵗw̲ords rhs
172 _ ← (2 = t̲ally ws) ᵖᵃr̲equire ("bad-convolution right", "the signal and the kernel, two names: \"x h\", not " c̲at rhs)
173 _ ← ('∧ r̲/ 1 c̲at '{ w → ᵖᵃn̲ame? d̲isclose w } e̲ach ws) ᵖᵃr̲equire ("bad-convolution right", "the signal and the kernel are names: " c̲at rhs)
174 (d̲isclose f̲irst ws, d̲isclose 2 s̲elect ws)
175}
176
177⍝│ The first of the candidates that is not the free index, as the sum's
178⍝│ index.
179ʰo̲therIndex ← { n (a, b, c) →
180 n̲ot n m̲atch a ? a
181 n̲ot n m̲atch b ? b
182 c
183}
184
185⍝━━ Reading the call
186
187⍝│ The free indices and their extents, from "Y[o,h,w] | O Ho Wo", as
188⍝│ (names, extents), boxed texts; "L[] | " gives none of either.
189ʰf̲ree ← { lhs →
190 l ← "|" ᵖᵃs̲plit lhs
191 _ ← (2 = t̲ally l) ᵖᵃr̲equire ("bad-equation left", "the result, then | and its extents: \"Y[o,h,w] | O Ho Wo\"")
192 head ← d̲isclose f̲irst l
193 _ ← (0 < t̲ally w̲here head m̲ember? "[") ᵖᵃr̲equire ("bad-equation left", "the result names its free indices in brackets: Y[o,h,w], or L[] for a scalar")
194 (_, names) ← "bad-equation left" ᵖᵃr̲eference head
195 extents ← " " ᵖᵃs̲plit d̲isclose f̲irst 1 d̲rop l
196 _ ← ((t̲ally names) = t̲ally extents) ᵖᵃr̲equire ("bad-equation left", "one extent for each free index: " c̲at lhs)
197 (names, extents)
198}
199
200⍝│ The right text, checked: its characters are the grammar's, a bare
201⍝│ sum is wrapped as the term it is, and the reading written back
202⍝│ equals the text with its spaces removed (so no rule dropped a
203⍝│ character); the text to read.
204ʰc̲hecked ← { rhs →
205 t ← ᵗt̲rim rhs
206 _ ← (0 < t̲ally t) ᵖᵃr̲equire ("bad-equation right", "an empty equation")
207 bad ← (n̲ot t m̲ember? alphabet) r̲eplicate t
208 0 < t̲ally bad ? "bad-equation right" ⎕R̲EJECT "not a character of an equation (names, numbers, + - * / ^, parentheses, brackets, a sum's | := and ..): " c̲at (1 t̲ake bad) c̲at " in " c̲at t
209 whole ← ʰw̲hole t
210 back ← ʰp̲lain whole
211 _ ← (back m̲atch ᵖᵃs̲queeze whole) ᵖᵃr̲equire ("bad-equation right", "the reader did not use all of the text: it read " c̲at back c̲at " from " c̲at t)
212 t
213}
214
215⍝│ A right side that is one bare sum, "sum i:=0..n | body", as the term
216⍝│ it is, parenthesized; anything else as written.
217ʰw̲hole ← { t →
218 0 < '+ r̲/ 0 + "|" ᵖᵃt̲op t ? "(" c̲at t c̲at ")"
219 t
220}
221
222⍝━━ The grammar, shared by the three readings
223
224⍝│ Characters of names and numbers.
225nameChars ← "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:"
226
227⍝│ Every character an equation may hold.
228alphabet ← nameChars c̲at ".+-*/^()[]|,= "
229
230⍝│ The operator words and the functions.
231words ← "sum" "prod" "max" "min" "integral" "scan"
232functions ← "exp" "log" "sqrt" "abs" "sin" "cos" "max" "min"
233
234⍝│ The quadrature rules an integral may be taken by, and the default
235⍝│ option text when a call writes none.
236rules ← "trapezoid" "simpson"
237defaultBy ← "trapezoid 64"
238
239⍝│ The spellings of an infinite bound.
240infinities ← "inf" "-inf" "+inf" "infinity" "-infinity" "oo" "-oo"
241
242⍝│ a when c holds, else b, both already computed: texts or counts (one each: a library's helpers form one binding group, in
243⍝│ which a helper used at two types is not yet generalized).
244ʰp̲ickText ← { c (a, b) →
245 c ? a
246 b
247}
248ʰp̲ickCount ← { c (a, b) →
249 c ? a
250 b
251}
252
253⍝│ What a term is, from its text (trimmed, not empty): "add" (a chain of
254⍝│ + and -, a leading - a negation), "mul" (* and /), "pow" (^), "sum"
255⍝│ (a parenthesized sum), "group" (parentheses), "fn" (a function
256⍝│ applied), "ref" (an array indexed) or "atom" (a name or a number).
257⍝│ One place decides, so the value, the LaTeX and the reprint agree.
258ʰk̲ind ← { t →
259 0 < '+ r̲/ 0 + "+-" ᵖᵃt̲op t ? "add"
260 0 < '+ r̲/ 0 + "*/" ᵖᵃt̲op t ? "mul"
261 0 < '+ r̲/ 0 + "^" ᵖᵃt̲op t ? "pow"
262 (ᵖᵃg̲roup? t) ∧ 0 < '+ r̲/ 0 + "|" ᵖᵃt̲op ᵖᵃi̲nner t ? "sum"
263 ᵖᵃg̲roup? t ? "group"
264 ʰf̲n? t ? "fn"
265 0 < t̲ally w̲here t m̲ember? "[" ? "ref"
266 "atom"
267}
268
269⍝│ Whether t is a function applied: a word, then one parenthesized
270⍝│ group that ends the text (pa:a_pplied?'s shape alone): the fixed
271⍝│ math functions and the program's own are both read this way
272⍝│ (X_eTaL-ML ask E3), told apart only in h:v_alueFn/h:t_exFn.
273ʰf̲n? ← { t → ᵖᵃa̲pplied? t }
274
275⍝│ A function applied, as (name, arguments), the arguments cut at the
276⍝│ top-level commas. The count is checked only for a fixed math
277⍝│ function (its own, not the caller's): a named function is trusted
278⍝│ with however many it is given, since nothing here knows its real
279⍝│ arity -- a wrong count is caught only when the expanded code fails
280⍝│ to compile (X_eTaL-ML ask E3, decided with the user).
281ʰf̲nParts ← { t →
282 (name, args) ← "bad-equation right" ᵖᵃa̲pplied t
283 known ← 0 < '+ r̲/ 0 + '{ f → (d̲isclose f) m̲atch name } e̲ach functions
284 wanted ← (("max" m̲atch name) ∨ ("min" m̲atch name)) ʰp̲ickCount (2, 1)
285 _ ← ((n̲ot known) ∨ (wanted = t̲ally args)) ᵖᵃr̲equire ("bad-equation right", name c̲at " takes " c̲at (f̲ormat wanted) c̲at " argument(s): " c̲at t)
286 (name, args)
287}
288
289⍝│ A parenthesized sum as (words, indices, lows, highs, options, body):
290⍝│ the bindings before the top-level |, each "word i:=low..high" (the
291⍝│ word an operator word), an integral's followed by its by options
292⍝│ (the text after by as written, "" for none).
293ʰs̲umParts ← { t →
294 inner ← ᵖᵃi̲nner t
295 cut ← "|" ᵖᵃt̲op inner
296 _ ← (1 = '+ r̲/ 0 + cut) ᵖᵃr̲equire ("bad-equation right", "a sum has its bindings, then one | and its body: " c̲at t)
297 at ← f̲irst w̲here cut
298 bindings ← "," ᵖᵃs̲plit (at − 1) t̲ake inner
299 body ← ᵗt̲rim at d̲rop inner
300 _ ← (0 < t̲ally body) ᵖᵃr̲equire ("bad-equation right", "a sum needs a body after |: " c̲at t)
301 ds ← '{ b → ʰb̲oxed ʰb̲inding d̲isclose b } m̲ap bindings
302 p̲art ← { k → '{ d → d̲isclose k s̲elect d̲isclose d } m̲ap ds }
303 (p̲art 1, p̲art 2, p̲art 3, p̲art 4, p̲art 5, body)
304}
305
306⍝│ One binding, "word i:=low..high", "integral x:=a..b by rule n" or
307⍝│ "scan t:=lo..hi by seed", as (word, index, low, high, options): the
308⍝│ by options cut off before Parsing reads the range, then checked
309⍝│ (an integral's rule and grid; a scan's seed, any expression of the
310⍝│ caller's values, checked only when it is read as one; a sum has
311⍝│ neither; an integral's bounds are finite).
312ʰb̲inding ← { b →
313 at ← " by " ᵖᵃa̲t b
314 core ← ᵗt̲rim at ʰu̲pTo b
315 opt ← ᵗt̲rim (at, 4) ʰp̲ast b
316 (w, i, lo, hi) ← ("bad-equation right", "an operator word", words) ᵖᵃb̲inding core
317 integral ← "integral" m̲atch w
318 scan ← "scan" m̲atch w
319 _ ← ((integral ∨ scan) ∨ 0 = at) ᵖᵃr̲equire ("bad-equation right", "by names an integral's rule and grid, or a scan's seed; a " c̲at w c̲at " takes none: " c̲at b)
320 _ ← (n̲ot integral ∧ (ʰi̲nfinite? lo) ∨ ʰi̲nfinite? hi) ᵖᵃr̲equire ("bad-equation right", "an improper integral (a bound of inf): write a truncation, 0..20 say, and show the tail is small, or change variables so the bounds are finite: " c̲at b)
321 _ ← ʰo̲ptionsOk (integral, at, opt)
322 (w, i, lo, hi, opt)
323}
324
325⍝│ Whether a bound is written infinite (inf, -inf, infinity, oo).
326ʰi̲nfinite? ← { b → '∨ r̲/ 0 + '{ f → (d̲isclose f) m̲atch b } e̲ach infinities }
327
328⍝│ The by options checked when there are any (at is where by was
329⍝│ found, 0 for none): an integral's rule and grid; a scan's seed is
330⍝│ any expression, read when the scan itself is (h:v_alueScan).
331ʰo̲ptionsOk ← { (integral, at, opt) →
332 0 = at ? 1 = 1
333 n̲ot integral ? 1 = 1
334 ʰo̲ption? opt
335}
336
337⍝│ Whether an integral's by options are a rule the library has and a
338⍝│ whole number of steps (even for simpson); else a refusal.
339ʰo̲ption? ← { opt →
340 ws ← ᵗw̲ords opt
341 _ ← (2 = t̲ally ws) ᵖᵃr̲equire ("bad-equation right", "after by: the rule and the grid, trapezoid 64 or simpson 32, not " c̲at opt)
342 rule ← d̲isclose f̲irst ws
343 n ← d̲isclose 2 s̲elect ws
344 _ ← ('∨ r̲/ 0 + '{ r → (d̲isclose r) m̲atch rule } e̲ach rules) ᵖᵃr̲equire ("bad-equation right", "the rule is trapezoid or simpson, not " c̲at rule)
345 _ ← ((0 < t̲ally n) ∧ '∧ r̲/ n m̲ember? ⎕D) ᵖᵃr̲equire ("bad-equation right", "the grid is a whole number of steps: " c̲at n)
346 k ← f̲irst f̲loor n̲umbers n
347 _ ← (0 < k) ᵖᵃr̲equire ("bad-equation right", "the grid needs at least one step: " c̲at n)
348 _ ← (n̲ot (rule m̲atch "simpson") ∧ 1 = k m̲od 2) ᵖᵃr̲equire ("bad-equation right", "simpson needs an even number of steps, not " c̲at n)
349 1 = 1
350}
351
352⍝│ The rule and the steps of an integral's options ("" for the default).
353ʰr̲uleSteps ← { opt →
354 ws ← ᵗw̲ords ((0 = t̲ally opt) ʰp̲ickText (defaultBy, opt))
355 (d̲isclose f̲irst ws, f̲irst f̲loor n̲umbers d̲isclose 2 s̲elect ws)
356}
357
358⍝│ s before position k (all of s when k is 0), and s after a separator
359⍝│ of length m at position k ("" when k is 0).
360ʰu̲pTo ← { k s →
361 0 = k ? s
362 (k − 1) t̲ake s
363}
364ʰp̲ast ← { (k, m) s →
365 0 = k ? ""
366 ((k + m) − 1) d̲rop s
367}
368
369⍝│ A binding's (word, index, low, high, options), boxed, so a sum's
370⍝│ bindings can be taken apart by position.
371ʰb̲oxed ← { (w, i, lo, hi, o) → (e̲nclose w) c̲at (e̲nclose i) c̲at (e̲nclose lo) c̲at (e̲nclose hi) c̲at e̲nclose o }
372
373⍝│ An atom checked: a name or a number.
374ʰa̲tom ← { t →
375 _ ← ((0 < t̲ally t) ∧ '× r̲/ 0 + t m̲ember? nameChars c̲at ".") ᵖᵃr̲equire ("bad-equation right", "not a name, number, A[i,j], function or (sum ...) in an equation: " c̲at t)
376 t
377}
378
379⍝━━ The value
380
381⍝│ The whole expansion: a block defining the checks, the grid of the
382⍝│ free indices, and the expression's value over it.
383ʰv̲alue ← { (names, extents) t →
384 helpers ← (⎕G̲ENSYM "a_t", ⎕G̲ENSYM "c_heck", ⎕G̲ENSYM "r_ank")
385 (at, chk, rk) ← helpers
386 grid ← ⎕G̲ENSYM "grid"
387 cell ← ⎕G̲ENSYM "g_rid"
388 spans ← '{ e → (e̲nclose "") ʰb̲ound d̲isclose e } m̲ap extents
389 lows ← (t̲ally names) r̲eshape e̲nclose "(0 + "
390 env ← (names, spans, lows, cell, helpers)
391 gridLines ← (0 = t̲ally names) ʰp̲ickText ("", ʰg̲ridLines (grid, cell, spans))
392 atLine ← " " c̲at at c̲at " := { A i -> (1 + i) s_elect r_avel A }\n"
393 ⍝ A value that names no index is a scalar: shaped to the grid, so the
394 ⍝ result has one axis per free index whatever the expression.
395 shaped ← (0 = t̲ally names) ʰp̲ickText ("", grid c̲at " r_eshape ")
396 "({ @ ->\n" c̲at atLine c̲at (ʰc̲hecks (chk, rk)) c̲at gridLines c̲at " " c̲at shaped c̲at "(" c̲at (env ʰv̲alueOf ʰw̲hole t) c̲at ")\n} @)"
397}
398
399⍝│ The lines defining a grid over spans and its cell function (the
400⍝│ coordinates along axis k, shaped like the grid).
401ʰg̲ridLines ← { (grid, cell, spans) →
402 " " c̲at grid c̲at " := " c̲at (" c_at " ʰj̲oinWith spans) c̲at "\n " c̲at cell c̲at " := { k -> " c̲at grid c̲at " r_eshape (o_ffsets k s_elect " c̲at grid c̲at ") 'l_eft t_able ('* r_/ k d_rop " c̲at grid c̲at ") r_eshape 0 }\n"
403}
404
405⍝│ The value of a term in env (the indices in scope, their spans and
406⍝│ lows, the grid's cell function, the helpers), as X_eTaL text.
407ʰv̲alueOf ← { env text →
408 t ← ᵗt̲rim text
409 k ← ʰk̲ind t
410 "add" m̲atch k ? env ʰv̲alueChain ("+-", t)
411 "mul" m̲atch k ? env ʰv̲alueChain ("*/", t)
412 "pow" m̲atch k ? env ʰv̲aluePow t
413 "sum" m̲atch k ? env ʰv̲alueSum t
414 "group" m̲atch k ? "(" c̲at (env ʰv̲alueOf ᵖᵃi̲nner t) c̲at ")"
415 "fn" m̲atch k ? env ʰv̲alueFn t
416 "ref" m̲atch k ? env ʰv̲alueRef t
417 env ʰv̲alueAtom ʰa̲tom t
418}
419
420⍝│ A chain folded left: ((a + b) - c); a leading minus is 0 - a.
421ʰv̲alueChain ← { env (ops, t) →
422 (parts, os, neg) ← ("bad-equation right", ops) ᵖᵃp̲arts t
423 first ← env ʰv̲alueOf d̲isclose f̲irst parts
424 acc ← neg ʰp̲ickText ("(0 - " c̲at first c̲at ")", first)
425 env ʰv̲alueFold (acc, os, 1 d̲rop parts)
426}
427
428⍝│ The left fold of a chain: acc, then each operator with the next part.
429ʰv̲alueFold ← { env (acc, os, parts) →
430 0 = t̲ally os ? acc
431 next ← "(" c̲at acc c̲at " " c̲at (1 t̲ake os) c̲at " " c̲at (env ʰv̲alueOf d̲isclose f̲irst parts) c̲at ")"
432 env ʰv̲alueFold (next, 1 d̲rop os, 1 d̲rop parts)
433}
434
435⍝│ a ^ b, right to left: the text cut at its first top-level ^.
436ʰv̲aluePow ← { env t →
437 i ← f̲irst w̲here "^" ᵖᵃt̲op t
438 "(" c̲at (env ʰv̲alueOf (i − 1) t̲ake t) c̲at " ^ " c̲at (env ʰv̲alueOf i d̲rop t) c̲at ")"
439}
440
441⍝│ A parenthesized sum: a block with its own grid over the indices in
442⍝│ scope and then its own, the body on that grid, and one reduction per
443⍝│ binding (innermost first), leaving an array over the indices in
444⍝│ scope. A scan is not a reduction (its term may use the one before,
445⍝│ X_eTaL-ML ask E2) and is handled on its own, h:v_alueScan, before
446⍝│ any of this -- it must be the term's only binding. Outside a scan,
447⍝│ prev is refused wherever it is written: the word is reserved for
448⍝│ the term a scan folds into the one before it.
449ʰv̲alueSum ← { (names, spans, lows, cell, helpers) t →
450 (ws, ns, ls, hs, os, body) ← ʰs̲umParts t
451 isScan ← 0 < '+ r̲/ 0 + '{ w → (d̲isclose w) m̲atch "scan" } e̲ach ws
452 _ ← ((n̲ot isScan) ∨ (1 = t̲ally ws)) ᵖᵃr̲equire ("bad-equation right", "a scan must be the only binding of its term (no sum, prod, max, min or integral alongside it): " c̲at t)
453 isScan ? ʰv̲alueScan ((names, spans, lows, cell, helpers), d̲isclose f̲irst ns, d̲isclose f̲irst ls, d̲isclose f̲irst hs, d̲isclose f̲irst os, body)
454 _ ← (n̲ot ʰh̲asPrev? body) ᵖᵃr̲equire ("bad-equation right", "prev names the term before inside a scan; it cannot be used here: " c̲at t)
455 n ← t̲ally names
456 _ ← ((t̲ally ns) = '+ r̲/ 0 + r̲avel (names c̲at ns) 'm̲atch t̲able ns) ᵖᵃr̲equire ("bad-equation right", "an index is bound once: " c̲at (", " ʰj̲oinWith (names c̲at ns)))
457 qs ← '{ k → names ʰq̲uadrature (d̲isclose k s̲elect ws, d̲isclose k s̲elect ls, d̲isclose k s̲elect hs, d̲isclose k s̲elect os) } m̲ap r̲ange t̲ally ns
458 q̲ ← { k j → d̲isclose j s̲elect d̲isclose k s̲elect qs }
459 own ← '{ k → k q̲ 1 } m̲ap r̲ange t̲ally ns
460 lows2 ← '{ k → k q̲ 2 } m̲ap r̲ange t̲ally ns
461 grid ← ⎕G̲ENSYM "grid"
462 cell2 ← ⎕G̲ENSYM "g_rid"
463 env ← (names c̲at ns, spans c̲at own, lows c̲at lows2, cell2, helpers)
464 reds ← "" ᵗj̲oin '{ k → (ʰr̲educer d̲isclose k s̲elect ws) c̲at " r_/_" c̲at (f̲ormat n + k) c̲at " " } m̲ap r̲ange t̲ally ns
465 weighted ← "" ᵗj̲oin '{ k → ʰw̲eighted (k q̲ 4, cell2, n + k) } m̲ap r̲ange t̲ally ns
466 none ← 0 = t̲ally spans c̲at own
467 lines ← ("" ᵗj̲oin '{ k → k q̲ 3 } m̲ap r̲ange t̲ally ns) c̲at none ʰp̲ickText ("", ʰg̲ridLines (grid, cell2, spans c̲at own))
468 shaped ← none ʰp̲ickText ("", grid c̲at " r_eshape ")
469 "({ @ ->\n" c̲at lines c̲at " " c̲at reds c̲at shaped c̲at weighted c̲at "(" c̲at (env ʰv̲alueOf body) c̲at ")\n} @)"
470}
471
472⍝│ The length of the leading run of name characters in t (0 if t does
473⍝│ not start with one): how h:r_enamed and h:h_asPrev? find a whole
474⍝│ word instead of matching part of a longer name.
475ʰw̲ordLen ← { t →
476 0 = t̲ally t ? 0
477 n̲ot (f̲irst t m̲ember? nameChars) ? 0
478 1 + ʰw̲ordLen 1 d̲rop t
479}
480
481⍝│ t with every whole-word occurrence of from replaced by to, anything
482⍝│ else untouched: how a scan's index and prev become plain generated
483⍝│ names before the body is translated like any other expression (its
484⍝│ own grammar has no notion of either, so they are never looked up
485⍝│ through the grid env the other bindings use).
486ʰr̲enamed ← { (from, to) t →
487 0 = t̲ally t ? t
488 n̲ot (f̲irst t m̲ember? nameChars) ? (1 t̲ake t) c̲at (from, to) ʰr̲enamed 1 d̲rop t
489 k ← ʰw̲ordLen t
490 word ← k t̲ake t
491 piece ← ({ @ → (word m̲atch from) ? to◆ word } @)
492 piece c̲at (from, to) ʰr̲enamed k d̲rop t
493}
494
495⍝│ Whether prev appears as a whole word anywhere in t: the word a
496⍝│ scan's own body may use (its term before), refused everywhere else.
497ʰh̲asPrev? ← { t →
498 0 = t̲ally t ? 1 = 0
499 n̲ot (f̲irst t m̲ember? nameChars) ? ʰh̲asPrev? 1 d̲rop t
500 k ← ʰw̲ordLen t
501 ((k t̲ake t) m̲atch "prev") ? 1 = 1
502 ʰh̲asPrev? k d̲rop t
503}
504
505⍝│ A scan's value: a genuine fold, not a reduction -- the array
506⍝│ machinery's reduce is a right fold (B6), only the same as a running
507⍝│ left-to-right accumulation when the combining step is associative,
508⍝│ which a scan's body need not be. Built instead from p_ower: one
509⍝│ state-threading step function, (t, prev) to (t's successor, the
510⍝│ body at that successor), applied once per point of the range,
511⍝│ starting one step before the low end so the first real application
512⍝│ lands there. prev starts at the seed (by, X_eTaL-ML ask E2); a
513⍝│ scan with no seed and a body that never names prev folds in a seed
514⍝│ of 0 harmlessly, since it is never read. The index and prev are
515⍝│ substituted into the body's own translation (not its text: that
516⍝│ would be reparsed as if it were the equation's grammar, which it
517⍝│ is not) by name, so a scan's free indices, references and nested
518⍝│ terms still translate through the ordinary env.
519ʰv̲alueScan ← { (env, index, lo, hi, seed, body) →
520 (names, _, _, _, _) ← env
521 loCode ← names ʰb̲ound lo
522 hiCode ← names ʰb̲ound hi
523 _ ← ((0 < t̲ally seed) ∨ (n̲ot (ʰh̲asPrev? body))) ᵖᵃr̲equire ("bad-equation right", "a scan whose body uses prev needs a seed: scan " c̲at index c̲at ":=" c̲at lo c̲at ".." c̲at hi c̲at " by seed | " c̲at body)
524 seedCode ← (0 = t̲ally seed) ʰp̲ickText ("0", names ʰb̲ound seed)
525 rawBody ← env ʰv̲alueOf body
526 (gStep, gT, gAcc, gNext) ← (⎕G̲ENSYM "step", ⎕G̲ENSYM "t", ⎕G̲ENSYM "acc", ⎕G̲ENSYM "t")
527 stepBody ← (index, gNext) ʰr̲enamed (("prev", gAcc) ʰr̲enamed rawBody)
528 "(({ @ ->\n " c̲at gStep c̲at " := ({ @ -> ((" c̲at hiCode c̲at ") >= (" c̲at loCode c̲at ")) ? 1; 0 - 1 } @)\n (_, " c̲at gAcc c̲at ") := (1 + a_bs ((" c̲at hiCode c̲at ") - (" c̲at loCode c̲at "))) '{ (" c̲at gT c̲at ", " c̲at gAcc c̲at ") ->\n " c̲at gNext c̲at " := " c̲at gT c̲at " + " c̲at gStep c̲at "\n (" c̲at gNext c̲at ", " c̲at stepBody c̲at ")\n } p_ower ((" c̲at loCode c̲at ") - " c̲at gStep c̲at ", " c̲at seedCode c̲at ")\n " c̲at gAcc c̲at "\n} @))"
529}
530
531⍝│ A binding's span, coordinate prefix, defining lines and weights
532⍝│ name, as texts: a sum's span is its range's length, checked at run
533⍝│ time (low and high are the caller's values, not known until then):
534⍝│ a negative length -- the range runs high..low, backwards -- signals
535⍝│ error[bad-equation] naming the word and the range as written,
536⍝│ rather than falling through to the array machinery's own count
537⍝│ error (X_eTaL-ML ask E1); its coordinate is its low plus the cell,
538⍝│ no lines, no weights; an integral's span is its grid's n + 1
539⍝│ points, its coordinate a + h * the cell, its lines define a, b, h
540⍝│ and the weights (fresh names, so the body may name any of a, b, h
541⍝│ or w), the weights holding the rule's and the step.
542ʰq̲uadrature ← { names (w, lo, hi, opt) →
543 n̲ot "integral" m̲atch w ? (e̲nclose "({ v -> (0 <= v) ? v; \"bad-equation\" []S_IGNAL \"" c̲at w c̲at "'s range runs low..high: " c̲at lo c̲at ".." c̲at hi c̲at "\" } (1 + " c̲at (names ʰb̲ound hi) c̲at " - " c̲at (names ʰb̲ound lo) c̲at "))") c̲at (e̲nclose "(" c̲at (names ʰb̲ound lo) c̲at " + ") c̲at (e̲nclose "") c̲at e̲nclose ""
544 (rule, k) ← ʰr̲uleSteps opt
545 (a, b, h, ws) ← (⎕G̲ENSYM "a", ⎕G̲ENSYM "b", ⎕G̲ENSYM "h", ⎕G̲ENSYM "w")
546 lines ← " " c̲at a c̲at " := f_loat (" c̲at (names ʰb̲ound lo) c̲at ")\n " c̲at b c̲at " := f_loat (" c̲at (names ʰb̲ound hi) c̲at ")\n " c̲at h c̲at " := (" c̲at b c̲at " - " c̲at a c̲at ") / " c̲at (f̲ormat k) c̲at ".0\n " c̲at ws c̲at " := " c̲at h c̲at " * " c̲at (rule ʰw̲eights k) c̲at "\n"
547 (e̲nclose "(" c̲at (f̲ormat k + 1) c̲at ")") c̲at (e̲nclose "(" c̲at a c̲at " + " c̲at h c̲at " * f_loat ") c̲at (e̲nclose lines) c̲at e̲nclose ws
548}
549
550⍝│ The rule's weights over k steps (k + 1 points), as code: trapezoid
551⍝│ 1/2, 1, ..., 1, 1/2; simpson (1, 4, 2, 4, ..., 4, 1) / 3.
552ʰw̲eights ← { rule k →
553 rule m̲atch "simpson" ? "(1.0 c_at (" c̲at (f̲ormat k − 1) c̲at " r_eshape 4.0 2.0) c_at 1.0) / 3.0"
554 "0.5 c_at (" c̲at (f̲ormat k − 1) c̲at " r_eshape 1.0) c_at 0.5"
555}
556
557⍝│ The factor an integral's binding puts before the body: its weights
558⍝│ laid over the grid along its axis; "" for a sum.
559ʰw̲eighted ← { (ws, cell, axis) →
560 0 = t̲ally ws ? ""
561 "((1 + " c̲at cell c̲at " " c̲at (f̲ormat axis) c̲at ") s_elect " c̲at ws c̲at ") * "
562}
563
564⍝│ The reduction an operator word stands for.
565ʰr̲educer ← { w →
566 "sum" m̲atch w ? "'+"
567 "prod" m̲atch w ? "'*"
568 "max" m̲atch w ? "'m_ax"
569 "integral" m̲atch w ? "'+"
570 "'m_in"
571}
572
573⍝│ A bound or extent: an expression of the caller's values, with no
574⍝│ index in scope; one that names an index is refused.
575ʰb̲ound ← { names t →
576 used ← '{ w → 0 < '+ r̲/ 0 + '{ n → (d̲isclose n) m̲atch d̲isclose w } e̲ach names } e̲ach ᵗw̲ords ʰs̲paced t
577 _ ← (0 = '+ r̲/ 0 + used) ᵖᵃr̲equire ("bad-equation right", "a bound or extent cannot depend on an index: " c̲at t)
578 (e̲nclose "", e̲nclose "", e̲nclose "", "", ("", "", "")) ʰv̲alueOf t
579}
580
581⍝│ t with its operators and brackets spaced out, so its words are its names.
582ʰs̲paced ← { t → "" ᵗj̲oin '{ ch → (ch m̲ember? "+-*/^()[],") ? " "◆ ch } m̲ap t }
583
584⍝│ A function applied to its arguments' values: the fixed math
585⍝│ functions by their own rule, any other name as the caller's own
586⍝│ u: function, applied to however many arguments it was given
587⍝│ (X_eTaL-ML ask E3) -- u:name(args), not a quoted call, so a name
588⍝│ that is not a function, or not one of this arity, is the expanded
589⍝│ code's own compile error, naming the equation's call.
590ʰv̲alueFn ← { env t →
591 (name, args) ← ʰf̲nParts t
592 vs ← '{ a → env ʰv̲alueOf d̲isclose a } m̲ap args
593 a ← d̲isclose f̲irst vs
594 "sqrt" m̲atch name ? "(" c̲at a c̲at " ^ 0.5)"
595 "exp" m̲atch name ? "(e_xp " c̲at a c̲at ")"
596 "log" m̲atch name ? "(l_og " c̲at a c̲at ")"
597 "abs" m̲atch name ? "(a_bs " c̲at a c̲at ")"
598 "sin" m̲atch name ? "(s_in " c̲at a c̲at ")"
599 "cos" m̲atch name ? "(c_os " c̲at a c̲at ")"
600 (("max" m̲atch name) ∨ ("min" m̲atch name)) ? "(" c̲at a c̲at (("max" m̲atch name) ʰp̲ickText (" m_ax ", " m_in ")) c̲at (d̲isclose 2 s̲elect vs) c̲at ")"
601 "(u:" c̲at name c̲at "(" c̲at (", " ᵗj̲oin vs) c̲at "))"
602}
603
604⍝│ A[e1,...,en] as a gather from A's ravel, the index by Horner's rule
605⍝│ over A's shape (0-origin, as the equation writes it); A is checked
606⍝│ to have n axes, and each index to lie within its axis.
607ʰv̲alueRef ← { (names, spans, lows, cell, (at, chk, rk)) t →
608 (name, ix) ← "bad-equation right" ᵖᵃr̲eference t
609 inner ← (names, spans, lows, cell, (at, chk, rk)) ʰh̲orner (name, ix, t̲ally ix)
610 checked ← "(" c̲at rk c̲at " (" c̲at name c̲at ", \"" c̲at name c̲at "\", " c̲at (f̲ormat t̲ally ix) c̲at ", \"" c̲at t c̲at "\"))"
611 "\n (" c̲at checked c̲at " " c̲at at c̲at " (" c̲at inner c̲at "))"
612}
613
614⍝│ The ravel position of A[e1,...,ek] by Horner's rule: ek + (k-th
615⍝│ extent of A) * (the same for e1 ... e(k-1)), down to e1; each ek
616⍝│ passes through the bounds check.
617ʰh̲orner ← { (names, spans, lows, cell, (at, chk, rk)) (name, ix, k) →
618 env ← (names, spans, lows, cell, (at, chk, rk))
619 written ← ᵗt̲rim d̲isclose k s̲elect ix
620 e ← "\n ((" c̲at name c̲at ", \"" c̲at name c̲at "\", " c̲at (f̲ormat k) c̲at ", \"" c̲at written c̲at "\") " c̲at chk c̲at " " c̲at (env ʰv̲alueOf written) c̲at ")"
621 k = 1 ? e
622 "(" c̲at e c̲at " + (" c̲at (f̲ormat k) c̲at " s_elect s_hape " c̲at name c̲at ") * " c̲at (env ʰh̲orner (name, ix, k − 1)) c̲at ")"
623}
624
625⍝│ An index in scope as its coordinates on the grid (its low added),
626⍝│ anything else as written: the caller's name or a number.
627ʰv̲alueAtom ← { (names, _, lows, cell, _) t →
628 hit ← w̲here '{ n → (d̲isclose n) m̲atch t } e̲ach names
629 0 = t̲ally hit ? t
630 k ← f̲irst hit
631 (d̲isclose k s̲elect lows) c̲at cell c̲at " " c̲at (f̲ormat k) c̲at ")"
632}
633
634⍝│ The two run-time checks the expansion defines: an index within its
635⍝│ axis (0 to the extent less one), and a reference with as many
636⍝│ indices as its array has axes; each names the array, the axis and
637⍝│ the expression as written, and gives its argument back when it holds.
638ʰc̲hecks ← { (chk, rk) →
639 c ← " " c̲at chk c̲at " := { (A, name, k, from) e ->\n n := k s_elect s_hape A\n bad := w_here n_ot r_avel (0 <= e) & e < n\n 0 = t_ally bad ? e\n \"index\" []S_IGNAL name c_at \" axis \" c_at (f_ormat k) c_at \": index \" c_at (f_ormat (f_irst bad) s_elect r_avel e) c_at \" is outside 0..\" c_at (f_ormat n - 1) c_at \" (from \" c_at from c_at \")\"\n }\n"
640 r ← " " c̲at rk c̲at " := { (A, name, q, ref) ->\n (t_ally s_hape A) = q ? A\n \"rank\" []S_IGNAL ref c_at \" indexes \" c_at (f_ormat q) c_at \" axes, but \" c_at name c_at \" has rank \" c_at f_ormat t_ally s_hape A\n }\n"
641 c c̲at r
642}
643
644⍝━━ The same call as LaTeX
645
646⍝│ The LaTeX text of the call (no quotes): the result as written (L for
647⍝│ a scalar), = and the expression; a bare sum without the parentheses
648⍝│ the reader gave it. on is 1 to mark the symbols (e:t_exSymbols<), 0
649⍝│ for plain LaTeX; every helper below takes the marking mk = (on, the
650⍝│ index names in scope), the free indices here, and each sum adds its
651⍝│ own for its bounds and body.
652ʰt̲ex ← { on lhs rhs →
653 (names, _) ← ʰf̲ree lhs
654 head ← ᵗt̲rim d̲isclose f̲irst "|" ᵖᵃs̲plit lhs
655 name ← ((f̲irst w̲here head m̲ember? "[") − 1) t̲ake head
656 sub ← (0 = t̲ally names) ʰp̲ickText ("", "_{" c̲at ("," ᵗj̲oin names) c̲at "}")
657 mk ← (on, names)
658 ((mk ʰm̲ark name)_ (ʰg̲reek name) c̲at sub) c̲at " = " c̲at mk ʰt̲exBody ᵗt̲rim rhs
659}
660
661⍝│ The LaTeX tex of the symbol name, wrapped in its classes when the
662⍝│ marking mk is on.
663ʰm̲ark ← { mk name tex →
664 (on, _) ← mk
665 0 = on ? tex
666 "\\\\htmlClass{sym sym-" c̲at name c̲at "}{" c̲at tex c̲at "}"
667}
668
669⍝│ The right side as LaTeX: a bare sum without the parentheses the
670⍝│ reader gives it, anything else as a term.
671ʰt̲exBody ← { mk t →
672 0 < '+ r̲/ 0 + "|" ᵖᵃt̲op t ? ((mk ʰt̲exSum ("", ""))_ t)
673 mk ʰt̲exOf ʰw̲hole t
674}
675
676⍝│ A term as LaTeX.
677ʰt̲exOf ← { mk text →
678 t ← ᵗt̲rim text
679 k ← ʰk̲ind t
680 "add" m̲atch k ? ((mk ʰt̲exChain "+-")_ t)
681 "mul" m̲atch k ? ((mk ʰt̲exChain "*/")_ t)
682 "pow" m̲atch k ? mk ʰt̲exPow t
683 "sum" m̲atch k ? ((mk ʰt̲exSum ("\\\\left(", "\\\\right)"))_ ᵖᵃi̲nner t)
684 "group" m̲atch k ? "\\\\left(" c̲at (mk ʰt̲exOf ᵖᵃi̲nner t) c̲at "\\\\right)"
685 "fn" m̲atch k ? mk ʰt̲exFn t
686 "ref" m̲atch k ? mk ʰt̲exRef t
687 mk ʰt̲exAtom t
688}
689
690⍝│ A name or a number: a Greek letter's name as its command; a named
691⍝│ scalar marked (not a number, an index in scope or prev).
692ʰt̲exAtom ← { mk text →
693 (_, ix) ← mk
694 t ← ʰa̲tom text
695 number ← (f̲irst t) m̲ember? "0123456789."
696 bound ← 0 < '+ r̲/ 0 + '{ w → (d̲isclose w) m̲atch t } e̲ach ix
697 number ∨ bound ∨ "prev" m̲atch t ? ʰg̲reek t
698 ((mk ʰm̲ark t)_ ʰg̲reek t)
699}
700
701⍝│ A chain: a + b - c; a \, b for a product; \frac{a}{b} for a quotient.
702ʰt̲exChain ← { mk ops t →
703 (parts, os, neg) ← ("bad-equation right", ops) ᵖᵃp̲arts t
704 p1 ← d̲isclose f̲irst parts
705 first ← ("/" m̲atch 1 t̲ake os c̲at " ") ʰp̲ickText (mk ʰt̲exBare p1, mk ʰt̲exOf p1)
706 acc ← neg ʰp̲ickText ("-" c̲at first, first)
707 mk ʰt̲exFold (acc, os, 1 d̲rop parts)
708}
709
710ʰt̲exFold ← { mk (acc, os, parts) →
711 0 = t̲ally os ? acc
712 op ← 1 t̲ake os
713 p ← d̲isclose f̲irst parts
714 next ← ("/" m̲atch op) ʰp̲ickText (mk ʰt̲exBare p, mk ʰt̲exOf p)
715 mk ʰt̲exFold (ʰt̲exJoin (acc, op, next), 1 d̲rop os, 1 d̲rop parts)
716}
717
718⍝│ A term without the parentheses it was written in, where what holds
719⍝│ it already sets it apart: the numerator or denominator of \frac, the
720⍝│ argument of \sqrt.
721ʰt̲exBare ← { mk text →
722 t ← ᵗt̲rim text
723 k ← ʰk̲ind t
724 "sum" m̲atch k ? ((mk ʰt̲exSum ("", ""))_ ᵖᵃi̲nner t)
725 "group" m̲atch k ? mk ʰt̲exOf ᵖᵃi̲nner t
726 mk ʰt̲exOf t
727}
728
729⍝│ Two operands joined by an operator: \frac for /, a thin space for *.
730ʰt̲exJoin ← { (acc, op, next) →
731 "/" m̲atch op ? "\\\\frac{" c̲at acc c̲at "}{" c̲at next c̲at "}"
732 "*" m̲atch op ? acc c̲at " \\\\, " c̲at next
733 acc c̲at " " c̲at op c̲at " " c̲at next
734}
735
736ʰt̲exPow ← { mk t →
737 i ← f̲irst w̲here "^" ᵖᵃt̲op t
738 (mk ʰt̲exOf (i − 1) t̲ake t) c̲at "^{" c̲at (mk ʰt̲exOf i d̲rop t) c̲at "}"
739}
740
741⍝│ A sum's bindings as \sum_{i=low}^{high} (the operator word's symbol),
742⍝│ then its body, wrapped as asked; its indices are in scope for its
743⍝│ bounds and its body.
744ʰt̲exSum ← { mk (open, close) inner →
745 (ws, ns, ls, hs, _, body) ← ʰs̲umParts "(" c̲at inner c̲at ")"
746 (on, ix) ← mk
747 m ← (on, ix c̲at ns)
748 o̲ne ← { k → (ʰs̲ymbol d̲isclose k s̲elect ws) c̲at "_{" c̲at (ʰt̲exIndex (d̲isclose k s̲elect ws, d̲isclose k s̲elect ns)) c̲at (m ʰt̲exOf d̲isclose k s̲elect ls) c̲at "}^{" c̲at (m ʰt̲exOf d̲isclose k s̲elect hs) c̲at "} " }
749 d̲ ← { k → ("integral" m̲atch d̲isclose k s̲elect ws) ʰp̲ickText (" \\\\, d" c̲at (ʰg̲reek d̲isclose k s̲elect ns), "") }
750 open c̲at ("" ᵗj̲oin 'o̲ne m̲ap r̲ange t̲ally ns) c̲at (m ʰt̲exOf body) c̲at ("" ᵗj̲oin 'd̲ m̲ap r̲ev r̲ange t̲ally ns) c̲at close
751}
752
753⍝│ A sum's index and = before its low; an integral's lower bound stands
754⍝│ alone (\int_{a}^{b}).
755ʰt̲exIndex ← { (w, i) →
756 "integral" m̲atch w ? ""
757 i c̲at "="
758}
759
760⍝│ A Greek letter's name as its LaTeX command (a capital's too: Phi,
761⍝│ Sigma); any other name as is.
762ʰg̲reek ← { v →
763 '∨ r̲/ 0 + '{ w → (d̲isclose w) m̲atch v } e̲ach ᵗw̲ords greek ? "\\\\" c̲at v
764 v
765}
766greek ← "alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi pi rho sigma tau upsilon phi chi psi omega Gamma Delta Theta Lambda Xi Pi Sigma Upsilon Phi Psi Omega"
767
768⍝│ The symbol of an operator word.
769ʰs̲ymbol ← { w →
770 "sum" m̲atch w ? "\\\\sum"
771 "prod" m̲atch w ? "\\\\prod"
772 "max" m̲atch w ? "\\\\max"
773 "integral" m̲atch w ? "\\\\int"
774 "scan" m̲atch w ? "\\\\operatorname{scan}"
775 "\\\\min"
776}
777
778⍝│ A function applied: \exp(x), \log(x), \sqrt{x}, \lvert x \rvert, \max(a, b).
779ʰt̲exFn ← { mk t →
780 (name, args) ← ʰf̲nParts t
781 as ← '{ a → mk ʰt̲exOf d̲isclose a } m̲ap args
782 a ← d̲isclose f̲irst as
783 known ← 0 < '+ r̲/ 0 + '{ f → (d̲isclose f) m̲atch name } e̲ach functions
784 "sqrt" m̲atch name ? "\\\\sqrt{" c̲at (mk ʰt̲exBare d̲isclose f̲irst args) c̲at "}"
785 "abs" m̲atch name ? "\\\\lvert " c̲at a c̲at " \\\\rvert"
786 known ? "\\\\" c̲at name c̲at "(" c̲at (", " ᵗj̲oin as) c̲at ")"
787 "\\\\operatorname{" c̲at name c̲at "}(" c̲at (", " ᵗj̲oin as) c̲at ")"
788}
789
790⍝│ A reference as a subscript: A_{i,j}, the indices as written without
791⍝│ spaces, a product among them as a thin space; marked as a whole.
792ʰt̲exRef ← { mk t →
793 (name, ix) ← "bad-equation right" ᵖᵃr̲eference t
794 ((mk ʰm̲ark name)_ name c̲at "_{" c̲at ("," ᵗj̲oin '{ i → ʰs̲ubscript d̲isclose i } m̲ap ix) c̲at "}")
795}
796
797ʰs̲ubscript ← { t → "" ᵗj̲oin '{ ch → (ch m̲ember? "*") ? "\\\\,"◆ (ch m̲ember? " ") ? ""◆ ch } m̲ap t }
798
799⍝━━ The reading written back
800
801⍝│ A term as it was read, with no spaces: the same text it came from,
802⍝│ or a rule dropped something.
803ʰp̲lain ← { text →
804 t ← ᵗt̲rim text
805 k ← ʰk̲ind t
806 "add" m̲atch k ? "+-" ʰp̲lainChain t
807 "mul" m̲atch k ? "*/" ʰp̲lainChain t
808 "pow" m̲atch k ? ʰp̲lainPow t
809 "sum" m̲atch k ? ʰp̲lainSum t
810 "group" m̲atch k ? "(" c̲at (ʰp̲lain ᵖᵃi̲nner t) c̲at ")"
811 "fn" m̲atch k ? ʰp̲lainFn t
812 "ref" m̲atch k ? ʰp̲lainRef t
813 ʰa̲tom t
814}
815
816ʰp̲lainChain ← { ops t →
817 (parts, os, neg) ← ("bad-equation right", ops) ᵖᵃp̲arts t
818 first ← ʰp̲lain d̲isclose f̲irst parts
819 acc ← neg ʰp̲ickText ("-" c̲at first, first)
820 ʰp̲lainFold (acc, os, 1 d̲rop parts)
821}
822
823ʰp̲lainFold ← { (acc, os, parts) →
824 0 = t̲ally os ? acc
825 ʰp̲lainFold (acc c̲at (1 t̲ake os) c̲at (ʰp̲lain d̲isclose f̲irst parts), 1 d̲rop os, 1 d̲rop parts)
826}
827
828ʰp̲lainPow ← { t →
829 i ← f̲irst w̲here "^" ᵖᵃt̲op t
830 (ʰp̲lain (i − 1) t̲ake t) c̲at "^" c̲at ʰp̲lain i d̲rop t
831}
832
833ʰp̲lainSum ← { t →
834 (ws, ns, ls, hs, os, body) ← ʰs̲umParts t
835 o̲ne ← { k → (d̲isclose k s̲elect ws) c̲at (d̲isclose k s̲elect ns) c̲at ":=" c̲at (ʰp̲lain d̲isclose k s̲elect ls) c̲at ".." c̲at (ʰp̲lain d̲isclose k s̲elect hs) c̲at ʰp̲lainBy d̲isclose k s̲elect os }
836 "(" c̲at ("," ᵗj̲oin 'o̲ne m̲ap r̲ange t̲ally ns) c̲at "|" c̲at (ʰp̲lain body) c̲at ")"
837}
838
839⍝│ An integral's by options as written, squeezed; "" for none.
840ʰp̲lainBy ← { opt →
841 0 = t̲ally opt ? ""
842 "by" c̲at ᵖᵃs̲queeze opt
843}
844
845ʰp̲lainFn ← { t →
846 (name, args) ← ʰf̲nParts t
847 name c̲at "(" c̲at ("," ᵗj̲oin '{ a → ʰp̲lain d̲isclose a } m̲ap args) c̲at ")"
848}
849
850ʰp̲lainRef ← { t →
851 (name, ix) ← "bad-equation right" ᵖᵃr̲eference t
852 name c̲at "[" c̲at ("," ᵗj̲oin '{ i → ʰp̲lain d̲isclose i } m̲ap ix) c̲at "]"
853}
854
855⍝━━ Text helpers
856
857⍝│ The boxed texts b joined by sep.
858ʰj̲oinWith ← { sep b → sep ᵗj̲oin b }