# Simplify regular expression? (Converting Unicode fractions to TeX)

Background

I'm converting Unicode text to TeX for typesetting. In the input, I'm allowing simple fractions like ½ and ⅔ using single Unicode characters and complex fractions like ¹²³/₄₅₆ using superscripted and subscripted numerals. First I convert the simple fractions (½ becomes \frac{1}{2}) and superscripted and subscripted numerals (¹²³ becomes $^{123}$ and ₄₅₆ becomes $_{456}$) using a character lookup table, then I make a second pass to collapse runs of numerals and combine numerator and denominator into a fraction (so ¹²³/₄₅₆ then becomes \frac{123}{456}). Finally, I make a third pass to insert a ⅙-em thin space between an integer and a fraction (so that, for example, 2¼ displays as 2 ¼).

Question(s)

Code works great, but I'm wondering how to simplify the regular expressions. There are three transformations.

• In the first transformation, is there a way to avoid the alternation operator (|) and simply match on either \^ or \_? I don't see a way to do it using ([\^\_]) and \1.
• Also in the first transformation, is there a way to avoid the nested substitutions?
• Is there some completely other solution to this that would work even better? (By better I don't mean faster but easier to understand.)

Here are the guts:

# First collapse runs of superscripted and/or subscripted numerals.
$text =~ s{ ( (?: $\^ [0-9]$ ){2,} # Match superscripted numerals | (?: $\_ [0-9]$ ){2,} # Match subscripted numerals ) }{ my$x = $1;$x =~ s{$}{}g; # Remove ''s x =~ s{(?<!^)[\^\_]}{}g; # Remove redundant '^'s and '_'s x =~ s{^(.)(.*)}{$${1}{$2}$}; # Wrap in '{}' and replace ''. x }xeg; # Now combine complete fractions. text =~ s{$ \^ \{? ([0-9]+) \}? $# 1 = numerator (?: / | \x{2044} ) # Slash or Fraction Slash$ \_ \{? ([0-9]+) \}? $# 2 = denominator }{ "\\frac{1}{2}" }xeg; # Finally, add a thin space between a whole number and a fraction. # (But do not add space between a whole number and an exponent.) text =~ s{(?<=[0-9])(?=\\frac\{.*?\}\{.*?\})}{\\,}g;  Note that this (probably) cannot be done in a single translation step, because it must also collapse superscripts like ¹²³ without an accompanying fraction, for if ¹²³ is left as ^1^2^3 (instead of collapsing it to ^{123}), then it will contain undesirable tiny spaces between the numerals. Additionally, it must convert 2¼ (add thin space) to 2 ¼ when it appears in the input using either the single character ¼ or the pair of superscript/subscript characters ¹ and ₄. Hence the very separate translation steps. Test cases Use ⅔ cup chopped garlic and 1½ cups chopped onion. Use \frac{2}{3} cup chopped garlic and 1\frac{1}{2} cups chopped onion. Use \frac{2}{3} cup chopped garlic and 1\,\frac{1}{2} cups chopped onion. This DeLorean DMC-12 runs on ²³⁹Pu and uses 1.21×10⁹ W. This DeLorean DMC-12 runs on ^2^3^9Pu and uses 1.21\times10^9 W. This DeLorean DMC-12 runs on ^{239}Pu and uses 1.21\times10^9 W. A googol is 10¹⁰⁰ A googol is 10^1^0^0 A googol is 10^{100} 1 ns = ¹/₁₀₀₀ µs 1 ns = ^1/_1_0_0_0 µs 1 ns = \frac{1}{1000} µs π ≅ ²²/₇ = 3¹/₇ π ≅ ^2^2/_7 = 3^1/_7 π ≅ \frac{22}{7} = 3\,\frac{1}{7} Complete working example Here is the complete program, with test cases: #!/usr/bin/perl -w use strict; use utf8; binmode STDOUT, ":utf8"; my unicode_to_tex = { # Unicode TeX "\x{2070}" => "$^0$", # ⁰ (superscript 0) "\x{00B9}" => "$^1$", # ¹ (superscript 1) "\x{00B2}" => "$^2$", # ² (superscript 2) "\x{00B3}" => "$^3$", # ³ (superscript 3) "\x{2074}" => "$^4$", # ⁴ (superscript 4) "\x{2075}" => "$^5$", # ⁵ (superscript 5) "\x{2076}" => "$^6$", # ⁶ (superscript 6) "\x{2077}" => "$^7$", # ⁷ (superscript 7) "\x{2078}" => "$^8$", # ⁸ (superscript 8) "\x{2079}" => "$^9$", # ⁹ (superscript 9) "\x{2080}" => "$_0$", # ₀ (subscript 0) "\x{2081}" => "$_1$", # ₁ (subscript 1) "\x{2082}" => "$_2$", # ₂ (subscript 2) "\x{2083}" => "$_3$", # ₃ (subscript 3) "\x{2084}" => "$_4$", # ₄ (subscript 4) "\x{2085}" => "$_5$", # ₅ (subscript 5) "\x{2086}" => "$_6$", # ₆ (subscript 6) "\x{2087}" => "$_7$", # ₇ (subscript 7) "\x{2088}" => "$_8$", # ₈ (subscript 8) "\x{2089}" => "$_9$", # ₉ (subscript 9) "\x{00BD}" => "\\frac{1}{2}", # ½ (fraction one half) "\x{2153}" => "\\frac{1}{3}", # ⅓ (fraction one third) "\x{2154}" => "\\frac{2}{3}", # ⅔ (fraction two thirds) "\x{00BC}" => "\\frac{1}{4}", # ¼ (fraction one fourth) "\x{00BE}" => "\\frac{3}{4}", # ¾ (fraction three fourths) "\x{2155}" => "\\frac{1}{5}", # ⅕ (fraction one fifth) "\x{2156}" => "\\frac{2}{5}", # ⅖ (fraction two fifths) "\x{2157}" => "\\frac{3}{5}", # ⅗ (fraction three fifths) "\x{2158}" => "\\frac{4}{5}", # ⅘ (fraction four fifths) "\x{2159}" => "\\frac{1}{6}", # ⅙ (fraction one sixth) "\x{215A}" => "\\frac{5}{6}", # ⅚ (fraction five sixths) "\x{215B}" => "\\frac{1}{8}", # ⅛ (fraction one eighth) "\x{215C}" => "\\frac{3}{8}", # ⅜ (fraction three eighths) "\x{215D}" => "\\frac{5}{8}", # ⅝ (fraction five eighths) "\x{215E}" => "\\frac{7}{8}", # ⅞ (fraction seven eighths) "\x{00D7}" => "$\\times$", # × (multiplication sign) }; sub convert() { my (text) = @_; text =~ s{(.)}{unicode_to_tex->{1}||1}eg; return text; } sub combine() { my (text) = @_; # First collapse runs of superscripted and/or subscripted numerals. text =~ s{ ( (?:$ \^ [0-9] $){2,} # Match superscripted numerals | (?:$ \_ [0-9] $){2,} # Match subscripted numerals ) }{ my x = 1; x =~ s{$}{}g;                      # Remove '$'s$x =~ s{(?<!^)[\^\_]}{}g;            # Remove redundant '^'s and '_'s
$x =~ s{^(.)(.*)$}{${1}{2}$};    # Wrap in '{}' and replace '$'.$x
}xeg;

# Now combine complete fractions.
$text =~ s{ $\^ \{? ([0-9]+) \}?$ #$1 = numerator
(?: / | \x{2044} )                   # Slash or Fraction Slash
$\_ \{? ([0-9]+) \}?$            # $2 = denominator }{ "\\frac{$1}{$2}" }xeg; # Finally, add a thin space between a whole number and a fraction. # (But do not add space between a whole number and an exponent.)$text =~ s{(?<=[0-9])(?=\\frac\{.*?\}\{.*?\})}{\\,}g;

return $text; } while (defined(my$text = <DATA>)) {
print $text;$text = convert($text); print$text;
$text = combine($text);