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 }