DKL9 GitList
Repositories
DKL9 home
rtensor
Code
Commits
Branches
Tags
Search
Tree:
1312198
Branches
Tags
master
rtensor
rtensor_to_html.js
Import v2.3-dev from a demonic ritual
dkl9
commited
1312198
at 2023-184 18:25:57
rtensor_to_html.js
Blame
History
Raw
"use strict"; // maths symbols for which the intuitive name matches the HTML entity (mostly Greek) const ENT_SYM = [ "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "lambda", "mu", "nu", "xi", "pi", "rho", "sigma", "tau", "phi", "psi", "omega", "Gamma", "Delta", "Theta", "Lambda", "Pi", "Sigma", "Phi", "Psi", "Omega", "Del", "part", "complexes", "naturals", "rationals", "reals", "integers", ]; const EXTRA_BUILTINS = new Set(["tan", "csc", "sec", "cot"]); // HTML named character references const NCR = { "α": { "codepoints":[945], "characters":"α" }, "β":{"codepoints":[946],"characters":"β"}, "γ":{"codepoints":[947],"characters":"γ"}, "δ":{"codepoints":[948],"characters":"δ"}, "ε":{"codepoints":[949],"characters":"ε"}, "ζ":{"codepoints":[950],"characters":"ζ"}, "η":{"codepoints":[951],"characters":"η"}, "θ":{"codepoints":[952],"characters":"θ"}, "λ":{"codepoints":[955],"characters":"λ"}, "μ":{"codepoints":[956],"characters":"μ"}, "ν":{"codepoints":[957],"characters":"ν"}, "ξ":{"codepoints":[958],"characters":"ξ"}, "π":{"codepoints":[960],"characters":"π"}, "ρ":{"codepoints":[961],"characters":"ρ"}, "σ":{"codepoints":[963],"characters":"σ"}, "τ":{"codepoints":[964],"characters":"τ"}, "φ":{"codepoints":[966],"characters":"φ"}, "ψ":{"codepoints":[968],"characters":"ψ"}, "ω":{"codepoints":[969],"characters":"ω"}, "Γ":{"codepoints":[915],"characters":"Γ"}, "Δ":{"codepoints":[916],"characters":"Δ"}, "Θ":{"codepoints":[920],"characters":"Θ"}, "Λ":{"codepoints":[923],"characters":"Λ"}, "Π":{"codepoints":[928],"characters":"Π"}, "Σ":{"codepoints":[931],"characters":"Σ"}, "Φ":{"codepoints":[934],"characters":"Φ"}, "′": { "codepoints": [8242], "characters": "\u2032" }, "Ψ":{"codepoints":[936],"characters":"Ψ"}, "Ω":{"codepoints":[937],"characters":"Ω"}, "∇": { "codepoints": [8711], "characters": "\u2207" }, "∂": { "codepoints": [8706], "characters": "\u2202" }, "ℂ": { "codepoints": [8450], "characters": "\u2102" }, "ℕ": { "codepoints": [8469], "characters": "\u2115" }, "ℚ": { "codepoints": [8474], "characters": "\u211A" }, "ℝ": { "codepoints": [8477], "characters": "\u211D" }, "ℤ": { "codepoints": [8484], "characters": "\u2124" }, // above: "Greek", below: operations "∧": { "codepoints": [8743], "characters": "\u2227" }, "≈": { "codepoints": [8776], "characters": "\u2248" }, "⨯": { "codepoints": [10799], "characters": "\u2A2F" }, "∈": { "codepoints": [8712], "characters": "\u2208" }, "∞": { "codepoints": [8734], "characters": "\u221E" }, "∫": { "codepoints": [8747], "characters": "\u222B" }, "⋂": { "codepoints": [8898], "characters": "\u22C2" }, "⌈": { "codepoints": [8968], "characters": "\u2308" }, "⌊": { "codepoints": [8970], "characters": "\u230A" }, "−": { "codepoints": [8722], "characters": "\u2212" }, "∨": { "codepoints": [8744], "characters": "\u2228" }, "±": { "codepoints": [177], "characters": "\u00B1" }, "∏": { "codepoints": [8719], "characters": "\u220F" }, "√": { "codepoints": [8730], "characters": "\u221A" }, "⌉": { "codepoints": [8969], "characters": "\u2309" }, "⌋": { "codepoints": [8971], "characters": "\u230B" }, "→": { "codepoints": [8594], "characters": "\u2192" }, "⋅": { "codepoints": [8901], "characters": "\u22C5" }, "∑": { "codepoints": [8721], "characters": "\u2211" }, "⋃": { "codepoints": [8899], "characters": "\u22C3" }, }; const ucFromNcr = s => NCR[`&${s};`].characters; const RTensorRender = { // special character from identifier, along with "greek" boolean unicodify(ident) { if (ident == "infty" || ident == "inf" || ident == "infinity") { return [ucFromNcr("infin"), false]; } else if (ident == "star") { return ["*", false]; } else if (ident == "etc") { return ["⋯", true]; } else if (ENT_SYM.includes(ident)) { return [ucFromNcr(ident), true]; } else { return [ident, false]; } }, // should this node get parenthesised for multiplication and such? shouldParenMul() { if ([1, 2, 4, 6].includes(this.ts)) { return false; } else if (this.ts == 3) { return this.m > 10 || [0, 1, 6, 9, 10].includes(this.m); } else if (this.ts == 5) { return this.l > 10 || [0, 1, 6, 9].includes(this.l); } else if ((this.ts == 7 || this.ts == 8) && this.l.ts == 1) { return ["all", "any", "if", "map", "pm", "sum"].includes(this.l.v); } else { return true; } }, // should this node get parenthesised as the base of exponentiation and such? shouldParenExp() { return (this.ts == 3 && this.l != 5) || this.shouldParenMul(); }, // fancy HTML version of the AST toHTML() { const lp = `<span class="big">(</span>`; const rp = `<span class="big">)</span>`; switch (this.ts) { case 1: // underscores indicate subscripts let sbu = this.v.replace(/'/g, ucFromNcr("prime")). split("_").map(s => RTensorRender.unicodify(s)). map(([s, b]) => { const s2 = new String(s); s2.greek = b; return s2; }); const cgify = s => s.greek ? `<span class="greek">${s}</span>` : s; const it = sbu.length > 1 ? `${cgify(sbu[0])}<sub>${sbu.slice(1).map(cgify).join("_")}</sub>`: cgify(sbu[0]); const spacify = sbu[0].replace(new RegExp(ucFromNcr("prime"), "g"), "").length >= 2 ? ` class="spaced"` : ``; return (BUILTIN_FUNCS[this.v] != null || EXTRA_BUILTINS.has(this.v)) ? `<var class="builtin">${it}</var>` : `<var${spacify}>${it}</var>`; break; case 2: return `${this.v}`; case 3: const l = this.l.toHTML(); const r = this.r.toHTML(); const pl = this.l.shouldParenMul() ? `${lp}${l}${rp}` : l; const pr = this.r.shouldParenMul() ? `${lp}${r}${rp}` : r; switch (this.m) { case 0: if ((this.r.ts == 7 || this.r.ts == 8) && this.r.l.v == "pm") { return `${l} ${r}`; } return `${l} + ${r}`; case 1: return `${l} ${ucFromNcr("minus")} ${pr}`; case 2: const dot = this.l.ts == 2 && this.r.ts == 2; return `${pl}${dot ? ` ${ucFromNcr("sdot")} ` : ""}${pr}`; case 3: return `<table class="frac"> <tr class="numer"><td>${l}</td></tr> <tr class="denom"><td>${r}</td></tr> </table>`; case 4: const spe = this.l.shouldParenExp(); return `${spe ? lp : ""}${l}${spe ? rp : ""}<sup>${r}</sup>`; case 5: return `log<sub>${l}</sub>${lp}${r}${rp}`; case 6: case 11: return `${l} = ${r}`; case 7: return `<table class="rboth"><tr> <td><sup><sup><sup class="rord">${l}</sup></sup></sup><span class="rcal">${ucFromNcr("radic")}</span></td> <td><span class="rcand">${r}</span></td> </tr></table>`; // TODO: case 8 not handled, because comma operator is confusing case 8: return `${l}, ${r}`; case 9: return `${l} < ${r}`; case 10: return `${l} ↦ ${r}`; default: return `${this.l.toHTML()} ? ${this.r.toHTML()}`; } break; case 4: return this.l ? `${this.m.toHTML()} = ${this.r.toHTML()}` : this.m.toHTML(); case 5: const m = this.m.toHTML(); const pm = this.m.shouldParenMul() ? `${lp}${m}${rp}` : m; switch (this.l) { case 0: return `+${pm}`; case 1: return ucFromNcr("minus") + pm; // case 2 not handled, because unary asterisk is not defined case 3: return `<table class="frac"> <tr class="numer"><td>1</td></tr> <tr class="denom"><td>${m}</td></tr> </table>`; case 4: return `<var>e</var><sup>${m}</sup>`; case 5: return `ln(${m})`; case 6: return `${m} = 0`; case 7: return `<table class="rboth"><tr> <td><span class="rcal">${ucFromNcr("radic")}</span></td> <td><span class="rcand">${m}</span></td> </tr></table>`; // likewise, no unary comma case 9: return `${m} < 0`; // likewise, no unary maplet // likewise, no unary assignment default: return `? ${m}`; } break; case 6: return `${this.l.toHTML()}[${this.m.toHTML()}]`; case 7: case 8: const fa = this.m[0] ? this.m[0].toHTML() : "?"; const sa = this.m[1] ? this.m[1].toHTML() : "?"; const ta = this.m[2] ? this.m[2].toHTML() : "?"; const al = this.m.map(x => x.toHTML()); // special functions if (this.l.ts == 1) { switch (this.l.v) { case "abs": return `|${fa}|`; case "all": return `${al.map(x => "(" + x + ")").join(` ${ucFromNcr("and")} `)}`; case "any": return `${al.map(x => "(" + x + ")").join(` ${ucFromNcr("or")} `)}`; case "approx": return `${fa} ${ucFromNcr("approx")} ${sa}`; case "at": return `${lp}${fa}${rp}<span class="ebar">|</span><table class="ebounds"> <tr><td>${this.m[2] ? ta : ""}</td></tr><tr><td>${sa}</td></tr> </table>`; case "brace": return `<span class="big">{</span>${fa}<span class="big">}</span>`; case "bracket": return `<span class="big">[</span>${fa}<span class="big">]</span>`; case "ceil": return ucFromNcr("lceil") + fa + ucFromNcr("rceil"); case "cross": return `${fa} ${ucFromNcr("Cross")} ${sa}`; case "dot": return `${fa} ${ucFromNcr("sdot")} ${sa}`; case "fact": const clp = this.m[0].shouldParenMul() ? lp : ""; const crp = this.m[0].shouldParenMul() ? rp : ""; return `${clp}${fa}${crp}!`; case "filter": return `{<var>x</var> ${ucFromNcr("in")} ${fa} | ${sa}(<var>x</var>)}`; case "floor": return ucFromNcr("lfloor") + fa + ucFromNcr("rfloor"); case "hat": return `${fa}̂`; case "if": return `<span class="big">{</span><table class="pwf"><tr> <td>${sa}</td> <td class="cond">if ${fa}</td> </tr><tr> <td>${ta}</td> <td class="cond">otherwise</td> </tr></table>`; case "in": return `${fa} <span class="greek">${ucFromNcr("in")}</span> ${sa}`; case "int": return this.m[1] ? `<span class="intsym">${ucFromNcr("int")}</span> <table class="intbs"> <tr class="intb"><td class="ub">${sa}</td></tr> <tr class="intb"><td>${fa}</td></tr> </table> ${ta}` : `<span class="intsym">${ucFromNcr("int")}</span> ${fa}`; case "intersection": return `${fa} <span class="greek">${ucFromNcr("Intersection")}</span> ${sa}`; case "lim": return `<table class="limls"> <tr><td>${sa}</td></tr> <tr><td>${true ? `<span class="big">${ucFromNcr("Lambda")}</span>` : "lim"}</td></tr> <tr><td>${fa}</td></tr> </table> ${ta}`; case "map": return `${fa} 𝚳 ${sa}`; case "matrix": const rc = parseInt(this.m[0]?.v || "2") || 2; const cc = parseInt(this.m[1]?.v || "2") || 2; return `<table class="mat"> <tr><td>${(new Array(rc)).fill(0).map((_x, i) => this.m.slice(2 + i * cc, 2 + (i + 1) * cc).map(el => el.toHTML()).join("</td><td>")). join("</td></tr>\n<tr><td>")}</td></tr> </table>`; case "paren": return `${lp}${fa}${rp}`; case "pm": return `${ucFromNcr("plusmn")} ${this.m[0].shouldParenMul() ? lp + fa + rp : fa}`; case "product": return `<table class="sum"> <tr><td>${sa}</td></tr> <tr><td><span class="big">${ucFromNcr("Product")}</span></td></tr> <tr><td>${fa}</td></tr> </table> ${ta}`; case "sum": return `<table class="sum"> <tr><td>${sa}</td></tr> <tr><td><span class="big">${ucFromNcr("sum")}</span></td></tr> <tr><td>${fa}</td></tr> </table> ${ta}`; case "union": return `${fa} <span class="greek">${ucFromNcr("Union")}</span> ${sa}`; case "vec": // TODO: there's a better way (include notation in variable names) const m = fa.match(/(.*)(\<sub\>.*\<\/sub\>)?(.*?)/); //alert(`${m[1]} and ${m[2]} and ${m[3]}`); return `${m[1]}⃗${m[2] || ""}${m[3] || ""}`; } } // anything else return `${this.l.toHTML()}(${this.m.map(x => x.toHTML()).join(", ")})`; default: return "?"; } }, }; AST.prototype.shouldParenMul = RTensorRender.shouldParenMul; AST.prototype.shouldParenExp = RTensorRender.shouldParenExp; AST.prototype.toHTML = RTensorRender.toHTML;