Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Problem.

I'm trying to write a command similar to \newcommand, but I'm having problems with illegal parameter numbers: for reasons that I can't fix out, it's asking me to use parameters of the type ##1 rather than #1. I'm familiar with using the former in the case of nested definitions, e.g. in code-snippets such as \def\foo#1{\def\bar##1{#1-##1}}; but I don't understand why the error is coming up in what I'm trying to do. I'm hoping that someone can show me how to make parameters of the style #1 suffice, in the example I describe below.

Details.

I'm trying to write a macro called \newmacro, which emulates the syntax of \newcommand, and which makes each macro write its own definition to an output file the first time it is used. Try out the minimal example below to see precisely what I mean:

\documentclass{article}
\makeatletter

\newwrite\macro@outfile
\immediate\openout\macro@outfile=\jobname.macros%
\typeout{Writing extra macros to file \jobname.macros}%

\def\macro@write#1#2{%
    \immediate\write\macro@outfile{\string\renewcommand\string#1#2}%
    \renewcommand#1#2%
    #1%
}

\def\newmacro#1[#2]#3{%
    \def#1{\macro@write{#1}{[#2]{#3}}}}

\makeatother

These macro definitions don't quite work properly: for instance, using the above pre-amble, the second invocation of \newmacro below throws an error.

\begin{document}

\newmacro\testmacro[1]{foo(##1)}
\testmacro{1}
\testmacro{2}

\newmacro\testmacro[1]{foo --- #1 ---}
\testmacro{3}
\testmacro{4}

\end{document}

If this document is compiled with the preamble above as test.tex, in addition to the usual output, a new file test.macros is created which will have the following content:

\renewcommand\testmacro[1]{foo(##1)}
\renewcommand\testmacro[1]{foo --- ##1 ---}

This is almost correct, and it is what I mean by the macro writing "its own definition" to an output file. However, I would like the stored definitions to use the parameters #1 rather than ##1, and for the invocation of \newmacro with the parameter #1 not to throw errors. I'm hoping that someone can show me how to do this.

share|improve this question
add comment

2 Answers

up vote 6 down vote accepted

When you say:

\newmacro\testmacro[1]{foo --- #1 ---}

You end up with:

\def\testmacro{\macro@write{\testmacro}{[1]{foo --- #1 ---}}}}

So you try to define a macro without any arguments but still use #1 in it. This leads to the Illegal parameter number error message.

In order to permit #1, #2, etc. you would need to define the macro with the right number of arguments. You can define the macro first with nine arguments to avoid any Illegal parameter number error. Then you expand it with ##1 etc. as arguments, so you basically replace #1 with ##1, etc., and define this to the same macro but without arguments. Because all this happens inside another macro \newmacro all # must be doubled:

\def\newmacro#1[#2]#3{%
    \def#1##1##2##3##4##5##6##7##8##9{\macro@write{#1}{[#2]{#3}}}% Define macro in a safe way
    \expandafter\def\expandafter#1\expandafter{#1{####1}{####2}{####3}{####4}{####5}{####6}{####7}{####8}{####9}}% Replace `#1` with `##1` etc.
}

Then, however, you have the issues of ## in your output file. This can be best avoided by again defining the macro with nine arguments and print the \meaning with the prefix stripped, before defining it to its true definition:

\def\macro@write#1#2{%
    \def#1##1##2##3##4##5##6##7##8##9{#2}%
    \immediate\write\macro@outfile{\string\renewcommand\string#1\expandafter\strip@prefix\meaning#1}%
    \renewcommand#1#2%
    #1%
}

The \jobname.macros files then holds:

\renewcommand\testmacro[1]{foo(#1)}
\renewcommand\testmacro[1]{foo --- #1 ---}

However, this breaks when you now use ##1 inside the macro definition, e.g:

\newmacro\testmacro[1]{foo --- #1 ---\def\A##1{(##1)}}

So instead you would recommend the following approach: Define the macro to a different name and copy that definition to the real name inside \macro@write. You avoid thereby all issues with escaping of macro arguments. You can use \csname with \string#1 to build a second macro name which is unique to the official macro name.

\documentclass{article}
\makeatletter

\newwrite\macro@outfile
\immediate\openout\macro@outfile=\jobname.macros%
\typeout{Writing extra macros to file \jobname.macros}%

\def\newmacro#1[#2]#3{%
    \expandafter\newcommand\csname @\string#1\endcsname[#2]{#3}%
    \newcommand#1{\macro@write{#1}{#2}}%
}

\def\macro@write#1#2{%
    \expandafter\let\expandafter#1\csname @\string#1\endcsname
    \expandafter\let\csname @\string#1\endcsname\@undefined
    \immediate\write\macro@outfile{\string\newcommand\string#1[#2]{\expandafter\strip@prefix\meaning#1}}%
    #1%
}

\makeatother
\begin{document}

\newmacro\testmacroa[1]{foo(#1)}
\testmacroa{1}
\testmacroa{2}

\newmacro\testmacrob[1]{foo --- #1 ---}
\testmacrob{3}
\testmacrob{4}

\newmacro\testmacroc[1]{foo --- #1 ---\def\A##1{(##1)}}
\testmacroc{3}
\testmacroc{4}

\end{document}

I took the liberty to replace \renewcommand with \newcommand because its seems to be more logically for a \newmacro macro. If you really want \renewcommand, i.e. allow \newmacro to redefine its macro, then define \newmacro like:

\def\newmacro#1[#2]#3{%
    \expandafter\let\csname @\string#1\endcsname\empty
    \expandafter\renewcommand\csname @\string#1\endcsname[#2]{#3}%
    \def#1{\macro@write{#1}{#2}}%
}

The first line ensures that the macro exists in any case before \renewcommand is used.

share|improve this answer
    
thank you for your thorough response! I'm attempting to refine your answer to one which will allow for optional arguments, while still writing the appropriate definition to the *.macros file — a task which is complicated by the way that \newcommand constructs macros which take optional arguments. In the meantime, I'm accepting your answer. –  Niel de Beaudrap May 21 '11 at 10:59
add comment

Epilogue.

As I noted in a comment to Martin's answer, there were additional obstacles which I found when I tried to extend my/his code to accept optional arguments in the style of \newcommand. I've accepted Martin's answer as resolving the problem which I posted: this answer is presented to show how to successfully treat both the cases of macros defined with \newcommand, both with optional arguments and without.

Recovering and writing the intended meaning of the macro.

The basic problem is that for macro \cseq which is meant to take an optional arguments, \newcommand defines two macros: \cseq itself, which looks for the optional argument, and \\cseq which actually operates on the arguments. So we need to do additional effort to extract the "meaning" of the \cseq for the purpose of writing to the file \jobname.macros.

We define a separate macro for "activating" a macro \cseq which is defined by \newmacro — giving \cseq its intended meaning, where it is temporarily stored in a macro \macro@cseq, while also extracting the "name" of the macro to \@tempa.

\def\macro@activate#1{%
    \edef\@tempa{\expandafter\@gobble\string#1}%
    \expandafter\let\expandafter#1\csname macro@\@tempa\endcsname
    \expandafter\let\csname macro@\@tempa\endcsname\@undefined
}

We will assume that the name of the macro (e.g. cseq) is stored in \@tempa when we want to write the intended meaning of \cseq to the output file. The following command then constructs the command sequence \macro@cseq, which it stores as the new meaning of \@tempa. Using this, it then writes the meaning of \\macro@cseq (the macro which actually acts on the arguments of \macro@cseq) to output: the macro \\macro@cseq is constructed on the final line essentially as \csname\string\macro@cseq\endcsname.

\def\macro@write@opts#1[#2][#3]{%
    \edef\@tempa{\expandafter\noexpand\csname macro@\@tempa\endcsname}%
    \def\@tempb##1{%
        \immediate\write\macro@outfile{\string\newcommand\string#1[#2][#3]{%
        \expandafter\strip@prefix\meaning##1}}}%
    \expandafter\expandafter\expandafter\@tempb%
        \expandafter\csname\expandafter\string\@tempa\endcsname%
}

The corresponding command to write macros with no arguments, or with no optional arguments, is simpler. In the macro below, #2 is either equal to the empty string or to [numargs].

\def\macro@write@noopts#1#2{%
    \immediate\write\macro@outfile{%
        \string\newcommand\string#1#2{\expandafter\strip@prefix\meaning#1}}%
}

The initial definition of the self-activating macros.

The remainder of the code is a more-or-less straightforward case analysis which determines if there are any parameters to the macro \cseq being defined, whether it has any optional arguments, and then redefining \cseq to activate (and write to file) it's intended behaviour before calling itself again. In the course of activating its intended behaviour, \@tempa comes to store the name of the macro (e.g. cseq), as we required in the case of a macro taking optional arguments.

\def\newmacro#1{%
    \edef\@tempa{\expandafter\@gobble\string#1}%
    \kernel@ifnextchar [{\newmacro@args#1}{\newmacro@noopts#1{}}}

\def\newmacro@args#1[#2]{%
    \kernel@ifnextchar [{\newmacro@opts#1{[#2]}}{\newmacro@noopts#1{[#2]}}}

\def\newmacro@noopts#1#2#3{%
    \expandafter\newcommand\csname macro@\@tempa\endcsname#2{#3}%
    \def#1{%
        \macro@activate#1%
        \macro@write@noopts#1{#2}%
        #1}}

\def\newmacro@opts#1#2[#3]#4{%
    \expandafter\newcommand\csname macro@\@tempa\endcsname#2[#3]{#4}%
    \def#1{%
        \macro@activate#1%
        \macro@write@opts#1#2[#3]%
        #1}}

Thanks again to Martin for his help.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.