macro librarywork/xetal-libraries/libs/Equations/src/Equations.xtlm
Equations: its macro library -- e_q<, t_ex< and b_oth<: an equation as printed in a paper, to its value, its LaTeX, or both. Imported with the functions, under one alias: "e:" u_se< "Equations". A macro is a function from the source text written left and right of its call to the source that replaces the call, run before the program is compiled (X_eTaL MC10). Names with m: are macros; those under h: are private to this file.
The macros
ᵐe̲q< : Char -> Char -> Char
The value of an equation written as a paper prints it. The left names the result, its free indices and their extents ("Y[o,h,w] | O Ho Wo", or "L[] | " for a scalar). The right is an expression over the free indices in which a parenthesized sum is a term: "(sum j:=0..M-1 | W[i,j] * x[j]) + b[i]" A sum is an operator word (sum, prod, max, min), an index, := and an inclusive range low..high, then | and its body; several, separated by commas, nest outermost first. A right side that is one bare sum needs no parentheses: "sum c:=0..C-1, sum r:=0..Kh-1 | X[c,h+r] * W[c,r]". The word integral binds a continuous variable over Float bounds and takes the value of its body by a quadrature rule written after by: "integral tau:=0..1 by simpson 64 | exp(tau)" (trapezoid or simpson, then the number of steps; without by, trapezoid 64; simpson needs an even number). The variable is the grid of n + 1 points a, a + h, ..., b; a bound of inf is refused (write a truncation and show the tail is small, or change variables). Trapezoid is exact on lines, simpson on cubics; at a jump of the body the sample takes the body's value at that point as written. Expressions follow math precedence: ^ binds tightest (right to left), then * and /, then + and - (left to right); parentheses group; the functions are exp(x), log(x), sqrt(x), abs(x), sin(x), cos(x), max(a, b) and min(a, b); A[e1,e2] reads one element of the caller's array A, one index per axis, from 0. Every reading rule uses all of its text or refuses the call, and what was read is written back and compared with the text at every call, so nothing written is ever dropped. Both sides are notations (X_eTaL MC32): where a program is drawn (the pages, the live demo, xetal render --html) the call's two strings are shown as one math box, what e:t_ex< gives for them, after the macro's name; the value and the text are unchanged. draws: t_ex<
ᵐe̲q< ← { lhs rhs → (ʰf̲ree lhs) ʰv̲alue ʰc̲hecked rhs }
ᵐt̲ex< : Char -> Char -> Char
The LaTeX of the same call, a string: the result as written (L for a scalar, Y_{o,h,w} for an array), then = and the expression, with \sum \prod \max \min, \int_{a}^{b} ... \, d\tau for an integral (a Greek letter's name as its command), \frac for a quotient, \sqrt \exp \log \sin \cos, and the brackets as subscripts.
ᵐt̲ex< ← { lhs rhs → "\"" c̲at ((0 ʰt̲ex lhs)_ ʰc̲hecked rhs) c̲at "\"" }
ᵐt̲exSymbols< : Char -> Char -> Char
The same LaTeX with each symbol the caller supplies marked for a page: the result, every array reference with its subscripts and every named scalar (in a body or a range bound) wrapped in KaTeX's \htmlClass{sym sym-NAME}{...}, NAME as written (a Greek letter by its word: sym-tau). Indices, an integral's variable, prev, functions and numbers are not wrapped (the classes idx and idx-NAME are kept for indices). Everything else is e:t_ex<'s output, unchanged. KaTeX draws \htmlClass only with trust allowing it and strict letting the htmlExtension through.
ᵐt̲exSymbols< ← { lhs rhs → "\"" c̲at ((1 ʰt̲ex lhs)_ ʰc̲hecked rhs) c̲at "\"" }
ᵐb̲oth< : Char -> Char -> Char
Both from one call: the pair (value, LaTeX); drawn as e:e_q< is. draws: t_ex<
ᵐ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 "\")" }
ᵐc̲onvolve< : Char -> Char -> Char
Discrete 1-D convolution and cross-correlation, written as the sums they are (X_eTaL Saga 42 decision 5: library macros over sum, not words of the grammar). The left names the result, its index and the boundary mode, "y[n] | full"; the right names the signal and the kernel, two of the caller's vectors, "x h". The modes: full gives L + M - 1 values (every overlap, the signal padded with M - 1 zeros at each end), same the L centered on full (as numpy centers them), valid the L - M + 1 complete overlaps (a kernel longer than the signal stops the program when it runs); no mode is implicit. Convolution is y[n] = sum over m of x[n - m] h[m], the kernel reversed as it slides; correlation y[n] = sum over m of x[n + m] h[m], not reversed, so correlation is convolution with the kernel reversed. xetal expand shows the sum each call becomes. Both sides are notations (MC32), drawn by e:c_onvolveTex<. draws: c_onvolveTex<
ᵐc̲onvolve< ← { lhs rhs → "convolve" ʰs̲lide (lhs, rhs) }
ᵐc̲orrelate< : Char -> Char -> Char
Cross-correlation, the same call shape (see c_onvolve<); drawn by e:c_orrelateTex<. draws: c_orrelateTex<
ᵐc̲orrelate< ← { lhs rhs → "correlate" ʰs̲lide (lhs, rhs) }
ᵐc̲onvolveTex< : Char -> Char -> Char
The LaTeX of a convolution call, a string: the textbook sum, y_{n} = \sum_{m} x_{n-m} \, h_{m}, with the mode after it in words.
ᵐc̲onvolveTex< ← { lhs rhs → "convolve" ʰs̲lideTex (lhs, rhs) }
ᵐc̲orrelateTex< : Char -> Char -> Char
The LaTeX of a correlation call (x_{n+m}, the kernel not reversed).
ᵐc̲orrelateTex< ← { lhs rhs → "correlate" ʰs̲lideTex (lhs, rhs) }
Convolution and correlation
ʰs̲lide : Char -> (Char, Char) -> Char
The expansion of a convolution or correlation call: a block that measures the signal (L) and the kernel (M), pads the signal with M - 1 zeros of its own type at each end (full and same), checks the lengths (valid), then the equation over the free index, computed as any equation is.
ʰs̲lide ← { op (lhs, rhs) → (y, n, mode) ← ʰs̲lideLeft lhs (x, h) ← ʰs̲lideRight rhs (gL, gM, gC, gP) ← (⎕G̲ENSYM "L", ⎕G̲ENSYM "M", ⎕G̲ENSYM "c", ⎕G̲ENSYM "p") m ← n ʰo̲therIndex ("m", "j", "q") 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" 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", "") 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")) signal ← ("valid" m̲atch mode) ʰp̲ickText (x, gP) shift ← ("full" m̲atch mode) ʰp̲ickText (gM c̲at "-1", ("same" m̲atch mode) ʰp̲ickText (gM c̲at "-1+" c̲at gC, "")) 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) 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 "]" "({ @ ->\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} @)" }
ʰs̲lideTex : Char -> (Char, Char) -> Char
The LaTeX of a convolution or correlation call as a string literal: the result and its index, = , the sum over the kernel's index of the signal at n - m (convolution) or n + m (correlation) times the kernel at m, then the mode in words; the sides read as the call is.
ʰs̲lideTex ← { op (lhs, rhs) → (y, n, mode) ← ʰs̲lideLeft lhs (x, h) ← ʰs̲lideRight rhs m ← n ʰo̲therIndex ("m", "j", "q") sign ← ("convolve" m̲atch op) ʰp̲ickText (" - ", " + ") "\"" 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 ")}\"" }
ʰp̲lus : Char -> Char
"+" and t, or "" for an empty t.
ʰp̲lus ← { t → 0 = t̲ally t ? "" "+" c̲at t }
ʰs̲lideLeft : Char -> (Char, Char, Char)
The left side "y[n] | mode" as (result, index, mode): one index, a mode the library has.
ʰs̲lideLeft ← { lhs → l ← "|" ᵖᵃs̲plit lhs _ ← (2 = t̲ally l) ᵖᵃr̲equire ("bad-convolution left", "the result, its index and the mode: \"y[n] | full\"") (y, ix) ← "bad-convolution left" ᵖᵃr̲eference d̲isclose f̲irst l _ ← (1 = t̲ally ix) ᵖᵃr̲equire ("bad-convolution left", "one index: \"y[n] | full\", not " c̲at lhs) mode ← ᵗt̲rim d̲isclose 2 s̲elect l _ ← (0 < t̲ally mode) ᵖᵃr̲equire ("bad-convolution left", "the mode is not implicit: write full, same or valid after |") _ ← ('∨ 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) (y, d̲isclose f̲irst ix, mode) }
ʰs̲lideRight : Char -> (Char, Char)
The right side "x h" as (signal, kernel): two names.
ʰs̲lideRight ← { rhs → ws ← ᵗw̲ords rhs _ ← (2 = t̲ally ws) ᵖᵃr̲equire ("bad-convolution right", "the signal and the kernel, two names: \"x h\", not " c̲at rhs) _ ← ('∧ 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) (d̲isclose f̲irst ws, d̲isclose 2 s̲elect ws) }
ʰo̲therIndex : Char -> (Char, Char, Char) -> Char
The first of the candidates that is not the free index, as the sum's index.
ʰo̲therIndex ← { n (a, b, c) → n̲ot n m̲atch a ? a n̲ot n m̲atch b ? b c }
Reading the call
ʰf̲ree : Char -> (Box Char, Box Char)
The free indices and their extents, from "Y[o,h,w] | O Ho Wo", as (names, extents), boxed texts; "L[] | " gives none of either.
ʰf̲ree ← { lhs → l ← "|" ᵖᵃs̲plit lhs _ ← (2 = t̲ally l) ᵖᵃr̲equire ("bad-equation left", "the result, then | and its extents: \"Y[o,h,w] | O Ho Wo\"") head ← d̲isclose f̲irst l _ ← (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") (_, names) ← "bad-equation left" ᵖᵃr̲eference head extents ← " " ᵖᵃs̲plit d̲isclose f̲irst 1 d̲rop l _ ← ((t̲ally names) = t̲ally extents) ᵖᵃr̲equire ("bad-equation left", "one extent for each free index: " c̲at lhs) (names, extents) }
ʰc̲hecked : Char -> Char
The right text, checked: its characters are the grammar's, a bare sum is wrapped as the term it is, and the reading written back equals the text with its spaces removed (so no rule dropped a character); the text to read.
ʰc̲hecked ← { rhs → t ← ᵗt̲rim rhs _ ← (0 < t̲ally t) ᵖᵃr̲equire ("bad-equation right", "an empty equation") bad ← (n̲ot t m̲ember? alphabet) r̲eplicate t 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 whole ← ʰw̲hole t back ← ʰp̲lain whole _ ← (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) t }
ʰw̲hole : Char -> Char
A right side that is one bare sum, "sum i:=0..n | body", as the term it is, parenthesized; anything else as written.
ʰw̲hole ← { t → 0 < '+ r̲/ 0 + "|" ᵖᵃt̲op t ? "(" c̲at t c̲at ")" t }
The grammar, shared by the three readings
nameChars : Char
Characters of names and numbers.
nameChars ← "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:"
alphabet : Char
Every character an equation may hold.
alphabet ← nameChars c̲at ".+-*/^()[]|,= "
words : Box Char
The operator words and the functions.
words ← "sum" "prod" "max" "min" "integral" "scan"
functions : Box Char
functions ← "exp" "log" "sqrt" "abs" "sin" "cos" "max" "min"
rules : Box Char
The quadrature rules an integral may be taken by, and the default option text when a call writes none.
rules ← "trapezoid" "simpson"
infinities : Box Char
The spellings of an infinite bound.
infinities ← "inf" "-inf" "+inf" "infinity" "-infinity" "oo" "-oo"
ʰp̲ickText : Truthy a => a -> (Char, Char) -> Char
a when c holds, else b, both already computed: texts or counts (one each: a library's helpers form one binding group, in which a helper used at two types is not yet generalized).
ʰp̲ickText ← { c (a, b) →
c ? a
b
}
ʰp̲ickCount : Truthy a => a -> (Int, Int) -> Int
ʰp̲ickCount ← { c (a, b) →
c ? a
b
}
ʰk̲ind : Char -> Char
What a term is, from its text (trimmed, not empty): "add" (a chain of + and -, a leading - a negation), "mul" (* and /), "pow" (^), "sum" (a parenthesized sum), "group" (parentheses), "fn" (a function applied), "ref" (an array indexed) or "atom" (a name or a number). One place decides, so the value, the LaTeX and the reprint agree.
ʰk̲ind ← { t → 0 < '+ r̲/ 0 + "+-" ᵖᵃt̲op t ? "add" 0 < '+ r̲/ 0 + "*/" ᵖᵃt̲op t ? "mul" 0 < '+ r̲/ 0 + "^" ᵖᵃt̲op t ? "pow" (ᵖᵃg̲roup? t) ∧ 0 < '+ r̲/ 0 + "|" ᵖᵃt̲op ᵖᵃi̲nner t ? "sum" ᵖᵃg̲roup? t ? "group" ʰf̲n? t ? "fn" 0 < t̲ally w̲here t m̲ember? "[" ? "ref" "atom" }
ʰf̲n? : Truthy a => Char -> a
Whether t is a function applied: a word, then one parenthesized group that ends the text (pa:a_pplied?'s shape alone): the fixed math functions and the program's own are both read this way (X_eTaL-ML ask E3), told apart only in h:v_alueFn/h:t_exFn.
ʰf̲n? ← { t → ᵖᵃa̲pplied? t }
ʰf̲nParts : Char -> (Char, Box Char)
A function applied, as (name, arguments), the arguments cut at the top-level commas. The count is checked only for a fixed math function (its own, not the caller's): a named function is trusted with however many it is given, since nothing here knows its real arity -- a wrong count is caught only when the expanded code fails to compile (X_eTaL-ML ask E3, decided with the user).
ʰf̲nParts ← { t → (name, args) ← "bad-equation right" ᵖᵃa̲pplied t known ← 0 < '+ r̲/ 0 + '{ f → (d̲isclose f) m̲atch name } e̲ach functions wanted ← (("max" m̲atch name) ∨ ("min" m̲atch name)) ʰp̲ickCount (2, 1) _ ← ((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) (name, args) }
ʰs̲umParts : Char -> (Box Char, Box Char, Box Char, Box Char, Box Char, Char)
A parenthesized sum as (words, indices, lows, highs, options, body): the bindings before the top-level |, each "word i:=low..high" (the word an operator word), an integral's followed by its by options (the text after by as written, "" for none).
ʰs̲umParts ← { t → inner ← ᵖᵃi̲nner t cut ← "|" ᵖᵃt̲op inner _ ← (1 = '+ r̲/ 0 + cut) ᵖᵃr̲equire ("bad-equation right", "a sum has its bindings, then one | and its body: " c̲at t) at ← f̲irst w̲here cut bindings ← "," ᵖᵃs̲plit (at − 1) t̲ake inner body ← ᵗt̲rim at d̲rop inner _ ← (0 < t̲ally body) ᵖᵃr̲equire ("bad-equation right", "a sum needs a body after |: " c̲at t) ds ← '{ b → ʰb̲oxed ʰb̲inding d̲isclose b } m̲ap bindings p̲art ← { k → '{ d → d̲isclose k s̲elect d̲isclose d } m̲ap ds } (p̲art 1, p̲art 2, p̲art 3, p̲art 4, p̲art 5, body) }
ʰb̲inding : Char -> (Char, Char, Char, Char, Char)
One binding, "word i:=low..high", "integral x:=a..b by rule n" or "scan t:=lo..hi by seed", as (word, index, low, high, options): the by options cut off before Parsing reads the range, then checked (an integral's rule and grid; a scan's seed, any expression of the caller's values, checked only when it is read as one; a sum has neither; an integral's bounds are finite).
ʰb̲inding ← { b → at ← " by " ᵖᵃa̲t b core ← ᵗt̲rim at ʰu̲pTo b opt ← ᵗt̲rim (at, 4) ʰp̲ast b (w, i, lo, hi) ← ("bad-equation right", "an operator word", words) ᵖᵃb̲inding core integral ← "integral" m̲atch w scan ← "scan" m̲atch w _ ← ((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) _ ← (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) _ ← ʰo̲ptionsOk (integral, at, opt) (w, i, lo, hi, opt) }
ʰi̲nfinite? : (Num a, Truthy a) => Char -> a
Whether a bound is written infinite (inf, -inf, infinity, oo).
ʰi̲nfinite? ← { b → '∨ r̲/ 0 + '{ f → (d̲isclose f) m̲atch b } e̲ach infinities }
ʰo̲ptionsOk : (Truthy a, Truthy b) => (a, Int, Char) -> b
The by options checked when there are any (at is where by was found, 0 for none): an integral's rule and grid; a scan's seed is any expression, read when the scan itself is (h:v_alueScan).
ʰo̲ptionsOk ← { (integral, at, opt) → 0 = at ? 1 = 1 n̲ot integral ? 1 = 1 ʰo̲ption? opt }
ʰo̲ption? : Truthy a => Char -> a
Whether an integral's by options are a rule the library has and a whole number of steps (even for simpson); else a refusal.
ʰo̲ption? ← { opt → ws ← ᵗw̲ords opt _ ← (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) rule ← d̲isclose f̲irst ws n ← d̲isclose 2 s̲elect ws _ ← ('∨ 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) _ ← ((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) k ← f̲irst f̲loor n̲umbers n _ ← (0 < k) ᵖᵃr̲equire ("bad-equation right", "the grid needs at least one step: " c̲at n) _ ← (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) 1 = 1 }
ʰr̲uleSteps : Char -> (Char, Int)
The rule and the steps of an integral's options ("" for the default).
ʰr̲uleSteps ← { opt → ws ← ᵗw̲ords ((0 = t̲ally opt) ʰp̲ickText (defaultBy, opt)) (d̲isclose f̲irst ws, f̲irst f̲loor n̲umbers d̲isclose 2 s̲elect ws) }
ʰu̲pTo : Int -> Char -> Char
s before position k (all of s when k is 0), and s after a separator of length m at position k ("" when k is 0).
ʰu̲pTo ← { k s → 0 = k ? s (k − 1) t̲ake s }
ʰp̲ast : (Int, Int) -> Char -> Char
ʰp̲ast ← { (k, m) s → 0 = k ? "" ((k + m) − 1) d̲rop s }
ʰb̲oxed : (Char, Char, Char, Char, Char) -> Box Char
A binding's (word, index, low, high, options), boxed, so a sum's bindings can be taken apart by position.
ʰ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 }
ʰa̲tom : Char -> Char
An atom checked: a name or a number.
ʰa̲tom ← { t → _ ← ((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) t }
The value
ʰv̲alue : (Box Char, Box Char) -> Char -> Char
The whole expansion: a block defining the checks, the grid of the free indices, and the expression's value over it.
ʰv̲alue ← { (names, extents) t → helpers ← (⎕G̲ENSYM "a_t", ⎕G̲ENSYM "c_heck", ⎕G̲ENSYM "r_ank") (at, chk, rk) ← helpers grid ← ⎕G̲ENSYM "grid" cell ← ⎕G̲ENSYM "g_rid" spans ← '{ e → (e̲nclose "") ʰb̲ound d̲isclose e } m̲ap extents lows ← (t̲ally names) r̲eshape e̲nclose "(0 + " env ← (names, spans, lows, cell, helpers) gridLines ← (0 = t̲ally names) ʰp̲ickText ("", ʰg̲ridLines (grid, cell, spans)) atLine ← " " c̲at at c̲at " := { A i -> (1 + i) s_elect r_avel A }\n" ⍝ A value that names no index is a scalar: shaped to the grid, so the ⍝ result has one axis per free index whatever the expression. shaped ← (0 = t̲ally names) ʰp̲ickText ("", grid c̲at " r_eshape ") "({ @ ->\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} @)" }
ʰg̲ridLines : (Char, Char, Box Char) -> Char
The lines defining a grid over spans and its cell function (the coordinates along axis k, shaped like the grid).
ʰg̲ridLines ← { (grid, cell, spans) → " " 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" }
ʰv̲alueOf : (Box Char, Box Char, Box Char, Char, (Char, Char, Char)) -> Char -> Char
The value of a term in env (the indices in scope, their spans and lows, the grid's cell function, the helpers), as X_eTaL text.
ʰv̲alueOf ← { env text → t ← ᵗt̲rim text k ← ʰk̲ind t "add" m̲atch k ? env ʰv̲alueChain ("+-", t) "mul" m̲atch k ? env ʰv̲alueChain ("*/", t) "pow" m̲atch k ? env ʰv̲aluePow t "sum" m̲atch k ? env ʰv̲alueSum t "group" m̲atch k ? "(" c̲at (env ʰv̲alueOf ᵖᵃi̲nner t) c̲at ")" "fn" m̲atch k ? env ʰv̲alueFn t "ref" m̲atch k ? env ʰv̲alueRef t env ʰv̲alueAtom ʰa̲tom t }
ʰv̲alueChain : (Box Char, Box Char, Box Char, Char, (Char, Char, Char)) -> (Char, Char) -> Char
A chain folded left: ((a + b) - c); a leading minus is 0 - a.
ʰv̲alueChain ← { env (ops, t) → (parts, os, neg) ← ("bad-equation right", ops) ᵖᵃp̲arts t first ← env ʰv̲alueOf d̲isclose f̲irst parts acc ← neg ʰp̲ickText ("(0 - " c̲at first c̲at ")", first) env ʰv̲alueFold (acc, os, 1 d̲rop parts) }
ʰv̲alueFold : (Box Char, Box Char, Box Char, Char, (Char, Char, Char)) -> (Char, Char, Box Char) -> Char
The left fold of a chain: acc, then each operator with the next part.
ʰv̲alueFold ← { env (acc, os, parts) → 0 = t̲ally os ? acc 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 ")" env ʰv̲alueFold (next, 1 d̲rop os, 1 d̲rop parts) }
ʰv̲aluePow : (Box Char, Box Char, Box Char, Char, (Char, Char, Char)) -> Char -> Char
a ^ b, right to left: the text cut at its first top-level ^.
ʰv̲aluePow ← { env t → i ← f̲irst w̲here "^" ᵖᵃt̲op t "(" c̲at (env ʰv̲alueOf (i − 1) t̲ake t) c̲at " ^ " c̲at (env ʰv̲alueOf i d̲rop t) c̲at ")" }
ʰv̲alueSum : (Box Char, Box Char, Box Char, Char, (Char, Char, Char)) -> Char -> Char
A parenthesized sum: a block with its own grid over the indices in scope and then its own, the body on that grid, and one reduction per binding (innermost first), leaving an array over the indices in scope. A scan is not a reduction (its term may use the one before, X_eTaL-ML ask E2) and is handled on its own, h:v_alueScan, before any of this -- it must be the term's only binding. Outside a scan, prev is refused wherever it is written: the word is reserved for the term a scan folds into the one before it.
ʰv̲alueSum ← { (names, spans, lows, cell, helpers) t → (ws, ns, ls, hs, os, body) ← ʰs̲umParts t isScan ← 0 < '+ r̲/ 0 + '{ w → (d̲isclose w) m̲atch "scan" } e̲ach ws _ ← ((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) 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) _ ← (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) n ← t̲ally names _ ← ((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))) 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 q̲ ← { k j → d̲isclose j s̲elect d̲isclose k s̲elect qs } own ← '{ k → k q̲ 1 } m̲ap r̲ange t̲ally ns lows2 ← '{ k → k q̲ 2 } m̲ap r̲ange t̲ally ns grid ← ⎕G̲ENSYM "grid" cell2 ← ⎕G̲ENSYM "g_rid" env ← (names c̲at ns, spans c̲at own, lows c̲at lows2, cell2, helpers) 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 weighted ← "" ᵗj̲oin '{ k → ʰw̲eighted (k q̲ 4, cell2, n + k) } m̲ap r̲ange t̲ally ns none ← 0 = t̲ally spans c̲at own 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)) shaped ← none ʰp̲ickText ("", grid c̲at " r_eshape ") "({ @ ->\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} @)" }
ʰw̲ordLen : Char -> Int
The length of the leading run of name characters in t (0 if t does not start with one): how h:r_enamed and h:h_asPrev? find a whole word instead of matching part of a longer name.
ʰw̲ordLen ← { t → 0 = t̲ally t ? 0 n̲ot (f̲irst t m̲ember? nameChars) ? 0 1 + ʰw̲ordLen 1 d̲rop t }
ʰr̲enamed : (Char, Char) -> Char -> Char
t with every whole-word occurrence of from replaced by to, anything else untouched: how a scan's index and prev become plain generated names before the body is translated like any other expression (its own grammar has no notion of either, so they are never looked up through the grid env the other bindings use).
ʰr̲enamed ← { (from, to) t → 0 = t̲ally t ? t n̲ot (f̲irst t m̲ember? nameChars) ? (1 t̲ake t) c̲at (from, to) ʰr̲enamed 1 d̲rop t k ← ʰw̲ordLen t word ← k t̲ake t piece ← ({ @ → (word m̲atch from) ? to◆ word } @) piece c̲at (from, to) ʰr̲enamed k d̲rop t }
ʰh̲asPrev? : Truthy a => Char -> a
Whether prev appears as a whole word anywhere in t: the word a scan's own body may use (its term before), refused everywhere else.
ʰh̲asPrev? ← { t → 0 = t̲ally t ? 1 = 0 n̲ot (f̲irst t m̲ember? nameChars) ? ʰh̲asPrev? 1 d̲rop t k ← ʰw̲ordLen t ((k t̲ake t) m̲atch "prev") ? 1 = 1 ʰh̲asPrev? k d̲rop t }
ʰv̲alueScan : ((Box Char, Box Char, Box Char, Char, (Char, Char, Char)), Char, Char, Char, Char, Char) -> Char
A scan's value: a genuine fold, not a reduction -- the array
machinery's reduce is a right fold (B6), only the same as a running
left-to-right accumulation when the combining step is associative,
which a scan's body need not be. Built instead from p_ower: one
state-threading step function, (t, prev) to (t's successor, the
body at that successor), applied once per point of the range,
starting one step before the low end so the first real application
lands there. prev starts at the seed (by, X_eTaL-ML ask E2); a
scan with no seed and a body that never names prev folds in a seed
of 0 harmlessly, since it is never read. The index and prev are
substituted into the body's own translation (not its text: that
would be reparsed as if it were the equation's grammar, which it
is not) by name, so a scan's free indices, references and nested
terms still translate through the ordinary env.
ʰv̲alueScan ← { (env, index, lo, hi, seed, body) → (names, _, _, _, _) ← env loCode ← names ʰb̲ound lo hiCode ← names ʰb̲ound hi _ ← ((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) seedCode ← (0 = t̲ally seed) ʰp̲ickText ("0", names ʰb̲ound seed) rawBody ← env ʰv̲alueOf body (gStep, gT, gAcc, gNext) ← (⎕G̲ENSYM "step", ⎕G̲ENSYM "t", ⎕G̲ENSYM "acc", ⎕G̲ENSYM "t") stepBody ← (index, gNext) ʰr̲enamed (("prev", gAcc) ʰr̲enamed rawBody) "(({ @ ->\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} @))" }
ʰq̲uadrature : Box Char -> (Char, Char, Char, Char) -> Box Char
A binding's span, coordinate prefix, defining lines and weights name, as texts: a sum's span is its range's length, checked at run time (low and high are the caller's values, not known until then): a negative length -- the range runs high..low, backwards -- signals error[bad-equation] naming the word and the range as written, rather than falling through to the array machinery's own count error (X_eTaL-ML ask E1); its coordinate is its low plus the cell, no lines, no weights; an integral's span is its grid's n + 1 points, its coordinate a + h * the cell, its lines define a, b, h and the weights (fresh names, so the body may name any of a, b, h or w), the weights holding the rule's and the step.
ʰq̲uadrature ← { names (w, lo, hi, opt) → 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 "" (rule, k) ← ʰr̲uleSteps opt (a, b, h, ws) ← (⎕G̲ENSYM "a", ⎕G̲ENSYM "b", ⎕G̲ENSYM "h", ⎕G̲ENSYM "w") 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" (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 }
ʰw̲eights : Char -> Int -> Char
The rule's weights over k steps (k + 1 points), as code: trapezoid 1/2, 1, ..., 1, 1/2; simpson (1, 4, 2, 4, ..., 4, 1) / 3.
ʰw̲eights ← { rule k → 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" "0.5 c_at (" c̲at (f̲ormat k − 1) c̲at " r_eshape 1.0) c_at 0.5" }
ʰw̲eighted : (Char, Char, Int) -> Char
The factor an integral's binding puts before the body: its weights laid over the grid along its axis; "" for a sum.
ʰw̲eighted ← { (ws, cell, axis) → 0 = t̲ally ws ? "" "((1 + " c̲at cell c̲at " " c̲at (f̲ormat axis) c̲at ") s_elect " c̲at ws c̲at ") * " }
ʰr̲educer : Char -> Char
The reduction an operator word stands for.
ʰr̲educer ← { w → "sum" m̲atch w ? "'+" "prod" m̲atch w ? "'*" "max" m̲atch w ? "'m_ax" "integral" m̲atch w ? "'+" "'m_in" }
ʰb̲ound : Box Char -> Char -> Char
A bound or extent: an expression of the caller's values, with no index in scope; one that names an index is refused.
ʰb̲ound ← { names t → used ← '{ w → 0 < '+ r̲/ 0 + '{ n → (d̲isclose n) m̲atch d̲isclose w } e̲ach names } e̲ach ᵗw̲ords ʰs̲paced t _ ← (0 = '+ r̲/ 0 + used) ᵖᵃr̲equire ("bad-equation right", "a bound or extent cannot depend on an index: " c̲at t) (e̲nclose "", e̲nclose "", e̲nclose "", "", ("", "", "")) ʰv̲alueOf t }
ʰs̲paced : Char -> Char
t with its operators and brackets spaced out, so its words are its names.
ʰs̲paced ← { t → "" ᵗj̲oin '{ ch → (ch m̲ember? "+-*/^()[],") ? " "◆ ch } m̲ap t }
ʰv̲alueFn : (Box Char, Box Char, Box Char, Char, (Char, Char, Char)) -> Char -> Char
A function applied to its arguments' values: the fixed math functions by their own rule, any other name as the caller's own u: function, applied to however many arguments it was given (X_eTaL-ML ask E3) -- u:name(args), not a quoted call, so a name that is not a function, or not one of this arity, is the expanded code's own compile error, naming the equation's call.
ʰv̲alueFn ← { env t → (name, args) ← ʰf̲nParts t vs ← '{ a → env ʰv̲alueOf d̲isclose a } m̲ap args a ← d̲isclose f̲irst vs "sqrt" m̲atch name ? "(" c̲at a c̲at " ^ 0.5)" "exp" m̲atch name ? "(e_xp " c̲at a c̲at ")" "log" m̲atch name ? "(l_og " c̲at a c̲at ")" "abs" m̲atch name ? "(a_bs " c̲at a c̲at ")" "sin" m̲atch name ? "(s_in " c̲at a c̲at ")" "cos" m̲atch name ? "(c_os " c̲at a c̲at ")" (("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 ")" "(u:" c̲at name c̲at "(" c̲at (", " ᵗj̲oin vs) c̲at "))" }
ʰv̲alueRef : (Box Char, Box Char, Box Char, Char, (Char, Char, Char)) -> Char -> Char
A[e1,...,en] as a gather from A's ravel, the index by Horner's rule over A's shape (0-origin, as the equation writes it); A is checked to have n axes, and each index to lie within its axis.
ʰv̲alueRef ← { (names, spans, lows, cell, (at, chk, rk)) t → (name, ix) ← "bad-equation right" ᵖᵃr̲eference t inner ← (names, spans, lows, cell, (at, chk, rk)) ʰh̲orner (name, ix, t̲ally ix) 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 "\"))" "\n (" c̲at checked c̲at " " c̲at at c̲at " (" c̲at inner c̲at "))" }
ʰh̲orner : (Box Char, Box Char, Box Char, Char, (Char, Char, Char)) -> (Char, Box Char, Int) -> Char
The ravel position of A[e1,...,ek] by Horner's rule: ek + (k-th extent of A) * (the same for e1 ... e(k-1)), down to e1; each ek passes through the bounds check.
ʰh̲orner ← { (names, spans, lows, cell, (at, chk, rk)) (name, ix, k) → env ← (names, spans, lows, cell, (at, chk, rk)) written ← ᵗt̲rim d̲isclose k s̲elect ix 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 ")" k = 1 ? e "(" 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 ")" }
ʰv̲alueAtom : (Box Char, Box Char, Box Char, Char, (Char, Char, Char)) -> Char -> Char
An index in scope as its coordinates on the grid (its low added), anything else as written: the caller's name or a number.
ʰv̲alueAtom ← { (names, _, lows, cell, _) t → hit ← w̲here '{ n → (d̲isclose n) m̲atch t } e̲ach names 0 = t̲ally hit ? t k ← f̲irst hit (d̲isclose k s̲elect lows) c̲at cell c̲at " " c̲at (f̲ormat k) c̲at ")" }
ʰc̲hecks : (Char, Char) -> Char
The two run-time checks the expansion defines: an index within its axis (0 to the extent less one), and a reference with as many indices as its array has axes; each names the array, the axis and the expression as written, and gives its argument back when it holds.
ʰc̲hecks ← { (chk, rk) → 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" 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" c c̲at r }
The same call as LaTeX
ʰt̲ex : Num a => a -> Char -> Char -> Char
The LaTeX text of the call (no quotes): the result as written (L for a scalar), = and the expression; a bare sum without the parentheses the reader gave it. on is 1 to mark the symbols (e:t_exSymbols<), 0 for plain LaTeX; every helper below takes the marking mk = (on, the index names in scope), the free indices here, and each sum adds its own for its bounds and body.
ʰt̲ex ← { on lhs rhs → (names, _) ← ʰf̲ree lhs head ← ᵗt̲rim d̲isclose f̲irst "|" ᵖᵃs̲plit lhs name ← ((f̲irst w̲here head m̲ember? "[") − 1) t̲ake head sub ← (0 = t̲ally names) ʰp̲ickText ("", "_{" c̲at ("," ᵗj̲oin names) c̲at "}") mk ← (on, names) ((mk ʰm̲ark name)_ (ʰg̲reek name) c̲at sub) c̲at " = " c̲at mk ʰt̲exBody ᵗt̲rim rhs }
ʰm̲ark : Num a => (a, Box Char) -> Char -> Char -> Char
The LaTeX tex of the symbol name, wrapped in its classes when the marking mk is on.
ʰm̲ark ← { mk name tex → (on, _) ← mk 0 = on ? tex "\\\\htmlClass{sym sym-" c̲at name c̲at "}{" c̲at tex c̲at "}" }
ʰt̲exBody : Num a => (a, Box Char) -> Char -> Char
The right side as LaTeX: a bare sum without the parentheses the reader gives it, anything else as a term.
ʰt̲exBody ← { mk t → 0 < '+ r̲/ 0 + "|" ᵖᵃt̲op t ? ((mk ʰt̲exSum ("", ""))_ t) mk ʰt̲exOf ʰw̲hole t }
ʰt̲exOf : Num a => (a, Box Char) -> Char -> Char
A term as LaTeX.
ʰt̲exOf ← { mk text → t ← ᵗt̲rim text k ← ʰk̲ind t "add" m̲atch k ? ((mk ʰt̲exChain "+-")_ t) "mul" m̲atch k ? ((mk ʰt̲exChain "*/")_ t) "pow" m̲atch k ? mk ʰt̲exPow t "sum" m̲atch k ? ((mk ʰt̲exSum ("\\\\left(", "\\\\right)"))_ ᵖᵃi̲nner t) "group" m̲atch k ? "\\\\left(" c̲at (mk ʰt̲exOf ᵖᵃi̲nner t) c̲at "\\\\right)" "fn" m̲atch k ? mk ʰt̲exFn t "ref" m̲atch k ? mk ʰt̲exRef t mk ʰt̲exAtom t }
ʰt̲exAtom : Num a => (a, Box Char) -> Char -> Char
A name or a number: a Greek letter's name as its command; a named scalar marked (not a number, an index in scope or prev).
ʰt̲exAtom ← { mk text → (_, ix) ← mk t ← ʰa̲tom text number ← (f̲irst t) m̲ember? "0123456789." bound ← 0 < '+ r̲/ 0 + '{ w → (d̲isclose w) m̲atch t } e̲ach ix number ∨ bound ∨ "prev" m̲atch t ? ʰg̲reek t ((mk ʰm̲ark t)_ ʰg̲reek t) }
ʰt̲exChain : Num a => (a, Box Char) -> Char -> Char -> Char
A chain: a + b - c; a \, b for a product; \frac{a}{b} for a quotient.
ʰt̲exChain ← { mk ops t → (parts, os, neg) ← ("bad-equation right", ops) ᵖᵃp̲arts t p1 ← d̲isclose f̲irst parts first ← ("/" m̲atch 1 t̲ake os c̲at " ") ʰp̲ickText (mk ʰt̲exBare p1, mk ʰt̲exOf p1) acc ← neg ʰp̲ickText ("-" c̲at first, first) mk ʰt̲exFold (acc, os, 1 d̲rop parts) }
ʰt̲exFold : Num a => (a, Box Char) -> (Char, Char, Box Char) -> Char
ʰt̲exFold ← { mk (acc, os, parts) → 0 = t̲ally os ? acc op ← 1 t̲ake os p ← d̲isclose f̲irst parts next ← ("/" m̲atch op) ʰp̲ickText (mk ʰt̲exBare p, mk ʰt̲exOf p) mk ʰt̲exFold (ʰt̲exJoin (acc, op, next), 1 d̲rop os, 1 d̲rop parts) }
ʰt̲exBare : Num a => (a, Box Char) -> Char -> Char
A term without the parentheses it was written in, where what holds it already sets it apart: the numerator or denominator of \frac, the argument of \sqrt.
ʰt̲exBare ← { mk text → t ← ᵗt̲rim text k ← ʰk̲ind t "sum" m̲atch k ? ((mk ʰt̲exSum ("", ""))_ ᵖᵃi̲nner t) "group" m̲atch k ? mk ʰt̲exOf ᵖᵃi̲nner t mk ʰt̲exOf t }
ʰt̲exJoin : (Char, Char, Char) -> Char
Two operands joined by an operator: \frac for /, a thin space for *.
ʰt̲exJoin ← { (acc, op, next) → "/" m̲atch op ? "\\\\frac{" c̲at acc c̲at "}{" c̲at next c̲at "}" "*" m̲atch op ? acc c̲at " \\\\, " c̲at next acc c̲at " " c̲at op c̲at " " c̲at next }
ʰt̲exPow : Num a => (a, Box Char) -> Char -> Char
ʰt̲exPow ← { mk t → i ← f̲irst w̲here "^" ᵖᵃt̲op t (mk ʰt̲exOf (i − 1) t̲ake t) c̲at "^{" c̲at (mk ʰt̲exOf i d̲rop t) c̲at "}" }
ʰt̲exSum : Num a => (a, Box Char) -> (Char, Char) -> Char -> Char
A sum's bindings as \sum_{i=low}^{high} (the operator word's symbol), then its body, wrapped as asked; its indices are in scope for its bounds and its body.
ʰt̲exSum ← { mk (open, close) inner → (ws, ns, ls, hs, _, body) ← ʰs̲umParts "(" c̲at inner c̲at ")" (on, ix) ← mk m ← (on, ix c̲at ns) 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 "} " } d̲ ← { k → ("integral" m̲atch d̲isclose k s̲elect ws) ʰp̲ickText (" \\\\, d" c̲at (ʰg̲reek d̲isclose k s̲elect ns), "") } 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 }
ʰt̲exIndex : (Char, Char) -> Char
A sum's index and = before its low; an integral's lower bound stands alone (\int_{a}^{b}).
ʰt̲exIndex ← { (w, i) → "integral" m̲atch w ? "" i c̲at "=" }
ʰg̲reek : Char -> Char
A Greek letter's name as its LaTeX command (a capital's too: Phi, Sigma); any other name as is.
ʰg̲reek ← { v → '∨ r̲/ 0 + '{ w → (d̲isclose w) m̲atch v } e̲ach ᵗw̲ords greek ? "\\\\" c̲at v v }
greek : Char
greek ← "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"
ʰs̲ymbol : Char -> Char
The symbol of an operator word.
ʰs̲ymbol ← { w → "sum" m̲atch w ? "\\\\sum" "prod" m̲atch w ? "\\\\prod" "max" m̲atch w ? "\\\\max" "integral" m̲atch w ? "\\\\int" "scan" m̲atch w ? "\\\\operatorname{scan}" "\\\\min" }
ʰt̲exFn : Num a => (a, Box Char) -> Char -> Char
A function applied: \exp(x), \log(x), \sqrt{x}, \lvert x \rvert, \max(a, b).
ʰt̲exFn ← { mk t → (name, args) ← ʰf̲nParts t as ← '{ a → mk ʰt̲exOf d̲isclose a } m̲ap args a ← d̲isclose f̲irst as known ← 0 < '+ r̲/ 0 + '{ f → (d̲isclose f) m̲atch name } e̲ach functions "sqrt" m̲atch name ? "\\\\sqrt{" c̲at (mk ʰt̲exBare d̲isclose f̲irst args) c̲at "}" "abs" m̲atch name ? "\\\\lvert " c̲at a c̲at " \\\\rvert" known ? "\\\\" c̲at name c̲at "(" c̲at (", " ᵗj̲oin as) c̲at ")" "\\\\operatorname{" c̲at name c̲at "}(" c̲at (", " ᵗj̲oin as) c̲at ")" }
ʰt̲exRef : Num a => (a, Box Char) -> Char -> Char
A reference as a subscript: A_{i,j}, the indices as written without spaces, a product among them as a thin space; marked as a whole.
ʰt̲exRef ← { mk t → (name, ix) ← "bad-equation right" ᵖᵃr̲eference t ((mk ʰm̲ark name)_ name c̲at "_{" c̲at ("," ᵗj̲oin '{ i → ʰs̲ubscript d̲isclose i } m̲ap ix) c̲at "}") }
ʰs̲ubscript : Char -> Char
ʰs̲ubscript ← { t → "" ᵗj̲oin '{ ch → (ch m̲ember? "*") ? "\\\\,"◆ (ch m̲ember? " ") ? ""◆ ch } m̲ap t }
The reading written back
ʰp̲lain : Char -> Char
A term as it was read, with no spaces: the same text it came from, or a rule dropped something.
ʰp̲lain ← { text → t ← ᵗt̲rim text k ← ʰk̲ind t "add" m̲atch k ? "+-" ʰp̲lainChain t "mul" m̲atch k ? "*/" ʰp̲lainChain t "pow" m̲atch k ? ʰp̲lainPow t "sum" m̲atch k ? ʰp̲lainSum t "group" m̲atch k ? "(" c̲at (ʰp̲lain ᵖᵃi̲nner t) c̲at ")" "fn" m̲atch k ? ʰp̲lainFn t "ref" m̲atch k ? ʰp̲lainRef t ʰa̲tom t }
ʰp̲lainChain : Char -> Char -> Char
ʰp̲lainChain ← { ops t → (parts, os, neg) ← ("bad-equation right", ops) ᵖᵃp̲arts t first ← ʰp̲lain d̲isclose f̲irst parts acc ← neg ʰp̲ickText ("-" c̲at first, first) ʰp̲lainFold (acc, os, 1 d̲rop parts) }
ʰp̲lainFold : (Char, Char, Box Char) -> Char
ʰp̲lainFold ← { (acc, os, parts) → 0 = t̲ally os ? acc ʰ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) }
ʰp̲lainPow : Char -> Char
ʰp̲lainPow ← { t → i ← f̲irst w̲here "^" ᵖᵃt̲op t (ʰp̲lain (i − 1) t̲ake t) c̲at "^" c̲at ʰp̲lain i d̲rop t }
ʰp̲lainSum : Char -> Char
ʰp̲lainSum ← { t → (ws, ns, ls, hs, os, body) ← ʰs̲umParts t 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 } "(" c̲at ("," ᵗj̲oin 'o̲ne m̲ap r̲ange t̲ally ns) c̲at "|" c̲at (ʰp̲lain body) c̲at ")" }
ʰp̲lainBy : Char -> Char
An integral's by options as written, squeezed; "" for none.
ʰp̲lainBy ← { opt → 0 = t̲ally opt ? "" "by" c̲at ᵖᵃs̲queeze opt }
ʰp̲lainFn : Char -> Char
ʰp̲lainFn ← { t → (name, args) ← ʰf̲nParts t name c̲at "(" c̲at ("," ᵗj̲oin '{ a → ʰp̲lain d̲isclose a } m̲ap args) c̲at ")" }
ʰp̲lainRef : Char -> Char
ʰp̲lainRef ← { t → (name, ix) ← "bad-equation right" ᵖᵃr̲eference t name c̲at "[" c̲at ("," ᵗj̲oin '{ i → ʰp̲lain d̲isclose i } m̲ap ix) c̲at "]" }
Text helpers
ʰj̲oinWith : Char -> Box Char -> Char
The boxed texts b joined by sep.
ʰj̲oinWith ← { sep b → sep ᵗj̲oin b }