Example: a Unicode renderer#
Technique: rules only. This renderer improves the plain-text output with Unicode
subscripts, a centred dot for multiplication and proper comparison signs. It changes no
handlers and no layout; it inherits everything from ASCIIRenderer and adds four
rules.
The code#
"""
A plain-text renderer with nicer Unicode math: Unicode subscripts, a centred dot
for multiplication and proper comparison signs. Built from rules only.
"""
from handcalcs.renderers import ASCIIRenderer
SUBSCRIPTS = str.maketrans(
"0123456789aehijklmnoprstuvx+-=()",
"₀₁₂₃₄₅₆₇₈₉ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓ₊₋₌₍₎",
)
COMPARISONS = {"lte_op": " ≤ ", "gte_op": " ≥ ", "neq_op": " ≠ ", "lt_op": " < ", "gt_op": " > "}
class UnicodeRenderer(ASCIIRenderer):
name = "unicode"
@UnicodeRenderer.register("name:sym")
def unicode_subscripts(renderer, node, base_context):
"""``I_y`` -> ``Iᵧ`` when every character has a subscript form, else unchanged."""
head, *subscripts = node.identifier.split("_")
if not subscripts:
return node
tail = "".join(subscripts)
if all(char.translate(SUBSCRIPTS) != char for char in tail):
node.identifier = head + tail.translate(SUBSCRIPTS)
return node
@UnicodeRenderer.register("mult_op:pre")
def centred_dot(renderer, node, base_context):
node.symbol = "·"
return node
@UnicodeRenderer.register("lte_op:pre")
@UnicodeRenderer.register("gte_op:pre")
@UnicodeRenderer.register("neq_op:pre")
@UnicodeRenderer.register("lt_op:pre")
@UnicodeRenderer.register("gt_op:pre")
def comparison_signs(renderer, node, base_context):
node.symbol = COMPARISONS[node.type]
return node
The result#
## Web check
x_1 = 51.7
alpha_max = x_1 * 2
F_y = 350
if x_1 <= 1100 / sqrt(F_y):
web_class = 1
=========
Web check
=========
x₁ = 51.7
αₘₐₓ = x₁ · 2 = 51.7 · 2 = 103.4
F_y = 350
Since (x₁ ≤ 1100 / √(F_y)) -> (51.7 ≤ 1100 / √(350)) is True:
web_class = 1
How it works#
unicode_subscripts is a name:sym rule, because a name’s identifier only appears
in the symbolic step. It runs after the inherited swap_greeks rule (a subclass’s rules
run after its parent’s), so alpha_max has already become α_max when it sees it.
Unicode has no subscript form for some letters (y, w, b, …), so the rule leaves a
name alone unless every character can be converted, which is why F_y keeps its
underscore.
centred_dot and comparison_signs are pre rules, because operators appear in
both the symbolic and the numeric step. They set node.symbol from fixed values, so
running twice on the same node (once per step) is harmless. See
Rules see each node twice.
The comparison signs include their own spaces because the compare handler joins its
parts with no separator (a<b).
Try it#
Register a
pow_op:prerule that turns**with a non-number exponent into^.Add a
name:symrule that turns a trailing_primeinto′.