Behaviour changes in math mode when macro is defined dynamically
If you ask \show\dynmacro
after \begin{document}
you’ll be answered
> \dynmacro=macro:
->\protect \unhbox \voidb@x \hbox {O}.
which is nothing you’d like, isn't it? The problem is \xdef
. You're even lucky that something meaningful happens. With \textbf
instead of \text
you’d get several errors. You need just one step of expansion.
\documentclass{report}
\usepackage[english]{babel}
\usepackage{pgffor}
\usepackage{amsmath}
% defining a plain macro
\def\macro{\text{O}}
% defining it dynamically
\foreach \mname/\cmd in {%
dynmacro/\text{O},%
} {%
\expandafter\gdef\csname\mname\expandafter\endcsname\expandafter{\cmd}%
}
\begin{document}
\show\dynmacro
\begin{equation}
2^{\text{O}} \times % okay
2^{\macro} \times % okay
2^{\dynmacro} % this one yields a weird result
\end{equation}
\end{document}
The console now will report
> \dynmacro=macro:
->\text {O}.
Important note: \text
is not the best for this, it should probably be \mathrm
.
A different implementation: \dyndef
wants three arguments
- the input form (optional, default
#1=#2
) - the definition to perform
- the list of things to define
Here are a couple of examples.
\documentclass{report}
\usepackage{amsmath}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\dyndef}{O{##1=##2}mm}
{
\cs_set:Npn \__iagolito_dyndef_aux:w #1 \q_stop { #2 }
\clist_map_function:nN { #3 } \iagolito_dyndef:n
}
\cs_new:Nn \iagolito_dyndef:n { \__iagolito_dyndef_aux:w #1 \q_stop }
\ExplSyntaxOff
% defining it dynamically
\dyndef{\expandafter\newcommand\csname#1\endcsname{#2}}{
dynmacro=\text{O},
dynmacroA=\mathbf{0},
}
\dyndef[#1/#2/#3]{\newcommand#1{#2-#3}}{
\foo/A/B,
}
\begin{document}
\begin{equation}
2^{\text{O}} \times 2^{\dynmacro} + 2^{\dynmacroA}+(\foo)
\end{equation}
\end{document}
As you see, the first example uses the default dynmacro=\text{O}
; for the second example, the specification of the argument is #1/#2/#3
; the same placeholders are used in the first mandatory argument.
The main problem is that \xdef
completely expands its argument, and this adds an \unhbox\voidb@x
to your \dynmacro
. Using etoolbox
's \expandonce
you can make the \cmd
expand once only (and using \csxdef
to make it simpler):
\documentclass{report}
\usepackage[english]{babel}
\usepackage{pgffor}
\usepackage{amsmath}
\usepackage{etoolbox}
% defining a plain macro
\def\macro{\text{O}}
% defining it dynamically
\foreach \mname/\cmd in {%
dynmacro/\text{O},%
} {%
\csxdef{\mname}{\expandonce\cmd}%
}
\begin{document}
\begin{equation}
2^{\text{O}} \times % okay
2^{\macro} \times % okay
2^{\dynmacro} % okay :)
\end{equation}
\end{document}
You can use a loop not encapsulating items in macros
\documentclass{report}
\usepackage[english]{babel}
\usepackage{pgffor}
\usepackage{amsmath}
\usepackage{xinttools}
% defining a plain macro
\def\macro{\text{O}}
\makeatletter % for LaTeX's \@namedef
% defining it dynamically
\xintForpair #1#2 in {%
(dynmacro, \text{O})%
% add more comma-separated "pairs" if needed
% (no ending comma, though, only to separate pairs)
}\do
{%
\@namedef{#1}{#2}%
}
\makeatother
\begin{document}
\begin{equation}
2^{\text{O}} \times % okay
2^{\macro} \times % okay
2^{\dynmacro} % this one is also okay
\end{equation}
% \showoutput
\end{document}