Using some code meant for upper-casing and lowercasing expandably (which you can grab there, and maybe at some point will become a package on CTAN), it turns out to be possible to produce a file with some macros expanded, with a reasonable speed, and relatively few lines of code.
First, let's see how it is used. Define your macros at the very top of the document (before \documentclass
), and set up everything related to the macro expansion. Then the command \ExpandMacros{macroexpander-example-out.tex}
grabs the end of the file inside an \edef
. The macro thus constructed is then written to the output file. That file is then input back, so that compiling your document still typesets it.
\input macroexpander.tex\relax
% Declare the commands that you want to see expanded between
% |\ExpandingCommands| and |\endExpandingCommands|. Note that
% for the moment, only |\newcommand| is supported, not
% |\newcommand*|, and that optional arguments are not
% supported (or rather, you'll be surprised by the result).
\ExpandingCommands
\newcommand{\myvec}[3]{(#1,#2,#3)}
\newcommand{\Vcal}{\ensuremath{\mathcal{V}}}
\newcommand{\myitem}{\item[\fbox{$\bullet$}]}
\endExpandingCommands
% You can also force other commands to expand.
\MacexpExpanding\emph % ok, don't do it for |\emph|: I have no good example.
% In order to get a neat output, you can tell Mr. MacXP to put a newline
% before outputting some commands.
\MacexpNewliner\begin
\MacexpNewliner\end
\MacexpNewliner\title
\MacexpNewliner\author
\MacexpNewliner\maketitle
\MacexpNewliner\item
% This is compatible with the |\newcommand| used above:
\MacexpNewliner\Vcal
% (Note that |\myitem| defined above expands to |\item|, which produces
% a newline, so we don't want |\MacexpNewliner\myitem|, which would add
% one more line!)
%
% The next line does the work. The argument is the name of the file where we
% shall write.
\ExpandMacros{macroexpander-example-out.tex}%
% ===============
\documentclass{article}
\begin{document}
\title{Example}
\author{Someone}
\maketitle
Let $x {= \myvec{x_1}{x_2}{x_3}}$ {b}e a \emph{nice} element of \Vcal.
Let $x = \myvec{x_1}{x_2}{x_3}$ be a nice element of \Vcal.
\begin{itemize}
\item
\myitem Let $x = \myvec{x_1}{x_2}{x_3}$ be a nice element of \Vcal.
\item Let $x = \myvec{x_1}{x_2}{x_3}$ be a nice element of \Vcal.
\myitem Let {$x = \myvec{x_1}{x_2}{x_3}$ be a nice element of} \Vcal.
\item
\myitem
\end{itemize}
\end{document}
The code itself is quite long. Basically, ULcase.sty
does all the heavy lifting of picking up tokens one by one (particularly spaces and braces). So we just need to decide what we do with each token, by specifying a table of case changes.
A short aside: in my view, there are three types of expandability: (the names are badly chosen... better names, anyone?)
the "strong" type is when hitting <token-list>
with a given number of expansions (with e.g., \expandafter
) will expand it fully.
the "medium" type if \romannumeral-`\0<token-list>
expands completely, or equivalently, if after enough expansions (coming from the left) we obtain the full expansion of <token-list>
.
the "weak" type is when \edef\foo{<token-list>}
works.
For example, \relax \empty
is only weakly expandable, \empty\relax
is "strongly" expandable, etc. (note: as Hendrik pointed out, calling \relax
expandable is just wrong, so the names should be changed). In our case, we will do \edef\foo{...
followed by the text we want to scan. Thus, we only need weak expandability overall. This means that at the outer level of grouping, we can simply output tokens to the left and continue. This will be key for efficiency. However, to treat brace groups, we need to get the full expansion of the content before outputting it. So we use a different "case-change" table, which outputs tokens at the end of the group, so that the expansion coming from the left can continue to act.
\begingroup
% Code based on the extended Upper- and Lower-casing code found
% in the ULcase package.
\input ULcase.sty\relax
% We use two tables of case change.
%
% - at the outer level of grouping, we use a table that outputs tokens
% on the left, |\unexpanded|. This is quick, but it will only work
% in the context of an |\edef|, |\write|, etc. Otherwise these tokens
% would go in the way of a further expansion.
%
% - inside brace groups, we need to expand "from the left" of the group:
% since we use |\romannumeral| to fully expand, only the left-most macros
% get expanded. So we need to output tokens by storing them at the end
% of the group, as we do usually (See |ULcase.sty|).
%
\long\gdef\UL_table_macexp_outer_default{\unexpanded}%
\long\gdef\UL_table_macexp_outer_braces#1#2{%
\unexpanded\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter{\UL_to_case:nn{macexp_inner}{#2}}%
}% Note that we use the |macexp_inner| table here.
}
% For the inner table, we just use the standard output, and keep the
% same table, |#1|.
\long\gdef\UL_table_macexp_inner_default{\UL_to_case_output:n}%
\long\gdef\UL_table_macexp_inner_braces#1#2{%
\expandafter\expandafter\expandafter\UL_to_case_output:n%
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter{\UL_to_case:nn{#1}{#2}}%
}%
}
%======= Customization.
% This long macro just initializes the table element |#1| of
% the tables |macexp_inner| and |macexp_outer| to consist
% in a |pre-| piece and a |the-| piece, which we configure separately
% later on (initially |{}|).
\long\gdef\macexp_set_init:N#1{%
\ifcsname UL_table_macexp_inner_\detokenize{#1}\endcsname
\expandafter\use_none:n
\else
\expandafter\use:n
\fi
{%
\long\expandafter\gdef\csname UL_table_macexp_inner_%
\detokenize{#1}\endcsname{%
\csname macexp_inner_pre_\detokenize{#1}\endcsname%
\csname macexp_inner_the_\detokenize{#1}\endcsname%
}%
\long\expandafter\gdef\csname UL_table_macexp_outer_%
\detokenize{#1}\endcsname{%
\csname macexp_outer_pre_\detokenize{#1}\endcsname%
\csname macexp_outer_the_\detokenize{#1}\endcsname%
}%
\long\expandafter\gdef\csname macexp_inner_pre_%
\detokenize{#1}\endcsname{}%
\long\expandafter\gdef\csname macexp_inner_the_%
\detokenize{#1}\endcsname{\UL_to_case_output:n{#1}}%
\long\expandafter\gdef\csname macexp_outer_pre_%
\detokenize{#1}\endcsname{}%
\long\expandafter\gdef\csname macexp_outer_the_%
\detokenize{#1}\endcsname{\unexpanded{#1}}%
}%
}
% Declaring a token to be expanding: this defines the |the-| piece
% mentionned above for both the inner and outer tables.
\long\gdef\macexp_set_expanding:N#1{%
\macexp_set_init:N#1%
\long\expandafter\gdef\csname macexp_inner_the_%
\detokenize{#1}\endcsname##1##2{\expandafter:nw{##1{##2}}#1}%
\long\expandafter\gdef\csname macexp_outer_the_%
\detokenize{#1}\endcsname##1##2{\expandafter:nw{##1{##2}}#1}%
}
% Declaring a token to induce a new-line in the output file
% (for readability). This is done with the |pre-| piece.
%
\begingroup\catcode`\^^M=12\relax%
\long\gdef\macexp_set_newliner:N#1{%
\macexp_set_init:N#1%
\long\expandafter\gdef\csname macexp_outer_pre_%
\detokenize{#1}\endcsname{\unexpanded{^^M}}%
\long\expandafter\gdef\csname macexp_inner_pre_%
\detokenize{#1}\endcsname{\UL_to_case_output:n{^^M}}%
}%
% And we define |\par| to give two lines when it is outside braces.
\long\expandafter\gdef\csname UL_table_macexp_outer_%
\detokenize{\par}\endcsname{^^M^^M}%
\endgroup
\long\gdef\macexp_newcommand#1{%
\macexp_set_expanding:N#1%
\macexp_old_newcommand{#1}%
}%
\long\gdef\ExpandingCommands{%
\let\macexp_old_newcommand\newcommand%
\let\newcommand\macexp_newcommand}%
\long\gdef\endExpandingCommands{%
\let\newcommand\macexp_old_newcommand}%
\long\gdef\ExpandMacros#1{%
\begingroup
\everyeof{%
{\q_mark} \q_stop\iffalse{\fi}%
\newlinechar=`\^^M\relax
\newwrite\macexp_write%
\immediate\openout\macexp_write=#1\relax%
\immediate\write\macexp_write{\unexpanded\expandafter{\macro_buffer}}%
\immediate\closeout\macexp_write%
\endgroup
\input #1\relax%
}%
\edef\macro_buffer{\iffalse}\fi%
\UL_brace_check:nw{macexp_outer}%
}
% User commands.
\long\gdef\MacexpNewliner{\macexp_set_newliner:N}
\long\gdef\MacexpExpanding{\macexp_set_expanding:N}
\endgroup
\foo
with it's replacement text for a few such macros.