Skip to content

Commit 44ee3d6

Browse files
committed
Limit Forward references in Mathtext parser
Comment indicates that limiting forward references is important for performance, but many more were used than needed. This is a 75% reduction in forward reference instances. Many of these are included in the definition of remaining forward references but do not themselves need to be a forward reference (indeed only 1 thing in a given cycle actually needs to be such that it can be referred to prior to its definition) `auto_delim` is recursively self-referential, and thus _must_ be all on its own `placeable` and `accent` form a tight loop, so one of those two needed to be a forward reference, and placeable was the easiest to keep (and most likely to be used in another definition that may introduce similar constraints) `token` is one of the more generic definitions, that is used in multiple other definitions. `required_group` and `optional_group` both caused failed tests when I tried to make them no longer forward references, has something to do with how `__call__` works for `Forward` vs `And` objects in pyparsing (though did not dig too much deeper into it, beyond noticing that the names in future `required_group("name")` calls still had error messages that say `"group"`, not their newer names, and that reverting to `Forward` instances fixed it). `operatorname` needed to move to avoid using forward references for `simple`, but does not actually have a cycle. Otherwise rewrapped some of the definitions to be one line and fixed a typing error that I noticed when looking into the parsing things.
1 parent 060992a commit 44ee3d6

File tree

2 files changed

+41
-65
lines changed

2 files changed

+41
-65
lines changed

lib/matplotlib/_mathtext.py

Lines changed: 40 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,8 +1817,11 @@ def __init__(self):
18171817
def set_names_and_parse_actions():
18181818
for key, val in vars(p).items():
18191819
if not key.startswith('_'):
1820-
# Set names on everything -- very useful for debugging
1821-
val.setName(key)
1820+
# Set names on (almost) everything -- very useful for debugging
1821+
# token, placeable, and auto_delim are forward references which
1822+
# are left without names to ensure useful error messages
1823+
if key not in ("token", "placeable", "auto_delim"):
1824+
val.setName(key)
18221825
# Set actions
18231826
if hasattr(self, key):
18241827
val.setParseAction(getattr(self, key))
@@ -1855,63 +1858,39 @@ def csnames(group, names):
18551858
p.unknown_symbol = Regex(r"\\[A-Za-z]*")("name")
18561859

18571860
p.font = csnames("font", self._fontnames)
1858-
p.start_group = (
1859-
Optional(r"\math" + oneOf(self._fontnames)("font")) + "{")
1861+
p.start_group = Optional(r"\math" + oneOf(self._fontnames)("font")) + "{"
18601862
p.end_group = Literal("}")
18611863

18621864
p.delim = oneOf(self._delims)
18631865

1864-
set_names_and_parse_actions() # for root definitions.
1865-
18661866
# Mutually recursive definitions. (Minimizing the number of Forward
18671867
# elements is important for speed.)
1868-
p.accent = Forward()
18691868
p.auto_delim = Forward()
1870-
p.binom = Forward()
1871-
p.customspace = Forward()
1872-
p.frac = Forward()
1873-
p.dfrac = Forward()
1874-
p.function = Forward()
1875-
p.genfrac = Forward()
1876-
p.group = Forward()
1877-
p.operatorname = Forward()
1878-
p.overline = Forward()
1879-
p.overset = Forward()
18801869
p.placeable = Forward()
18811870
p.required_group = Forward()
1882-
p.simple = Forward()
18831871
p.optional_group = Forward()
1884-
p.sqrt = Forward()
1885-
p.subsuper = Forward()
1886-
p.text = Forward()
18871872
p.token = Forward()
1888-
p.underset = Forward()
18891873

18901874
set_names_and_parse_actions() # for mutually recursive definitions.
18911875

1892-
p.customspace <<= cmd(r"\hspace", "{" + p.float_literal("space") + "}")
1876+
p.optional_group <<= "{" + ZeroOrMore(p.token)("group") + "}"
1877+
p.required_group <<= "{" + OneOrMore(p.token)("group") + "}"
18931878

1894-
p.accent <<= (
1879+
p.customspace = cmd(r"\hspace", "{" + p.float_literal("space") + "}")
1880+
1881+
p.accent = (
18951882
csnames("accent", [*self._accent_map, *self._wide_accents])
18961883
- p.placeable("sym"))
18971884

1898-
p.function <<= csnames("name", self._function_names)
1899-
p.operatorname <<= cmd(
1900-
r"\operatorname", "{" + ZeroOrMore(p.simple)("name") + "}")
1885+
p.function = csnames("name", self._function_names)
19011886

1902-
p.group <<= p.start_group + ZeroOrMore(p.token)("group") + p.end_group
1887+
p.group = p.start_group + ZeroOrMore(p.token)("group") + p.end_group
19031888

1904-
p.optional_group <<= "{" + ZeroOrMore(p.token)("group") + "}"
1905-
p.required_group <<= "{" + OneOrMore(p.token)("group") + "}"
1906-
1907-
p.frac <<= cmd(
1908-
r"\frac", p.required_group("num") + p.required_group("den"))
1909-
p.dfrac <<= cmd(
1910-
r"\dfrac", p.required_group("num") + p.required_group("den"))
1911-
p.binom <<= cmd(
1912-
r"\binom", p.required_group("num") + p.required_group("den"))
1889+
p.frac = cmd(r"\frac", p.required_group("num") + p.required_group("den"))
1890+
p.dfrac = cmd(r"\dfrac", p.required_group("num") + p.required_group("den"))
1891+
p.binom = cmd(r"\binom", p.required_group("num") + p.required_group("den"))
19131892

1914-
p.genfrac <<= cmd(
1893+
p.genfrac = cmd(
19151894
r"\genfrac",
19161895
"{" + Optional(p.delim)("ldelim") + "}"
19171896
+ "{" + Optional(p.delim)("rdelim") + "}"
@@ -1920,21 +1899,39 @@ def csnames(group, names):
19201899
+ p.required_group("num")
19211900
+ p.required_group("den"))
19221901

1923-
p.sqrt <<= cmd(
1902+
p.sqrt = cmd(
19241903
r"\sqrt{value}",
19251904
Optional("[" + OneOrMore(NotAny("]") + p.token)("root") + "]")
19261905
+ p.required_group("value"))
19271906

1928-
p.overline <<= cmd(r"\overline", p.required_group("body"))
1907+
p.overline = cmd(r"\overline", p.required_group("body"))
19291908

1930-
p.overset <<= cmd(
1909+
p.overset = cmd(
19311910
r"\overset",
19321911
p.optional_group("annotation") + p.optional_group("body"))
1933-
p.underset <<= cmd(
1912+
p.underset = cmd(
19341913
r"\underset",
19351914
p.optional_group("annotation") + p.optional_group("body"))
19361915

1937-
p.text <<= cmd(r"\text", QuotedString('{', '\\', endQuoteChar="}"))
1916+
p.text = cmd(r"\text", QuotedString('{', '\\', endQuoteChar="}"))
1917+
1918+
p.subsuper = (
1919+
(Optional(p.placeable)("nucleus")
1920+
+ OneOrMore(oneOf(["_", "^"]) - p.placeable)("subsuper")
1921+
+ Regex("'*")("apostrophes"))
1922+
| Regex("'+")("apostrophes")
1923+
| (p.placeable("nucleus") + Regex("'*")("apostrophes"))
1924+
)
1925+
1926+
p.simple = p.space | p.customspace | p.font | p.subsuper
1927+
1928+
p.token <<= (
1929+
p.simple
1930+
| p.auto_delim
1931+
| p.unknown_symbol # Must be last
1932+
)
1933+
1934+
p.operatorname = cmd(r"\operatorname", "{" + ZeroOrMore(p.simple)("name") + "}")
19381935

19391936
p.placeable <<= (
19401937
p.accent # Must be before symbol as all accents are symbols
@@ -1954,27 +1951,6 @@ def csnames(group, names):
19541951
| p.text
19551952
)
19561953

1957-
p.simple <<= (
1958-
p.space
1959-
| p.customspace
1960-
| p.font
1961-
| p.subsuper
1962-
)
1963-
1964-
p.subsuper <<= (
1965-
(Optional(p.placeable)("nucleus")
1966-
+ OneOrMore(oneOf(["_", "^"]) - p.placeable)("subsuper")
1967-
+ Regex("'*")("apostrophes"))
1968-
| Regex("'+")("apostrophes")
1969-
| (p.placeable("nucleus") + Regex("'*")("apostrophes"))
1970-
)
1971-
1972-
p.token <<= (
1973-
p.simple
1974-
| p.auto_delim
1975-
| p.unknown_symbol # Must be last
1976-
)
1977-
19781954
p.auto_delim <<= (
19791955
r"\left" - (p.delim("left") | Error("Expected a delimiter"))
19801956
+ ZeroOrMore(p.simple | p.auto_delim)("mid")

lib/matplotlib/mathtext.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ from typing import IO, Literal
1212
from matplotlib.typing import ColorType
1313

1414
class MathTextParser:
15-
def __init__(self, output: Literal["path", "raster", "macosx"]) -> None: ...
15+
def __init__(self, output: Literal["path", "agg", "raster", "macosx"]) -> None: ...
1616
def parse(self, s: str, dpi: float = ..., prop: FontProperties | None = ...): ...
1717

1818
def math_to_image(

0 commit comments

Comments
 (0)