3

I'm trying to expand a macro argument with a counter inside a for loop with the next code:

\documentclass{article}
\usepackage{xparse}
\usepackage{forloop}

\NewDocumentCommand\test{m O{II} O{III} O{IV}}{ \newcounter{i} \forloop{i}{1}{\thei < 5}{ #1\thei : #\thei \quad } }

\begin{document}

\test{A}

\end{document}

This yield two errors:

  1. ! Illegal parameter number in definition of \test code.

  2. ! You can't use `macro parameter character #' in horizontal mode.

I'm trying to achieve an output like this:

A1:A  A2:II  A3:III  A4:IV

i.e., I want to iterate through the macro parameters.

It seems that the problem is in the #\thei part. So some of the things I've tried so far are \expandafter #\thei or \expandafter #\numexpr\thei\relax without success.

I've been searching for information in TeX by Topic but I can't find anything helpful in this regard. I would appreciate some help if posssible!


Here I leave a fully compiling variant for testing purposes, this of course is not the desired behavior:

\documentclass{article}
\usepackage{xparse}
\usepackage{forloop}

\NewDocumentCommand\test{m O{II} O{III} O{IV}}{ \newcounter{i} \forloop{i}{1}{\thei < 5}{ #1\thei : #1 \quad } }

\begin{document}

\test{A}

\end{document}

which outputs:

A1:A  A2:A  A3:A  A4:A
  • What exactly do you want to do, maybe there is another way? – ljguo Mar 14 '22 at 08:49
  • IIRC TeX does not expand tokens when looking for a number following #, so this cannot really work. See e.g. https://tex.stackexchange.com/q/200283/82917. There might be other ways, but a detailed example of what the input and expected output should be would be helpful. – campa Mar 14 '22 at 09:12

4 Answers4

5

The symbols #1, #2 in macro body are interpreted as "points where actual parameter have to be inserted" and these symbols are saved to the macro body without any interpretation of surrounding tokens when \def is processed. It means that you cannot use #\foo where \foo is a macro which expands to 1.

Your task can be solved by following macros:

\newcount\tmpnum
\def\test #1{\tmpnum=0 \def\param{#1}\testA{}{II}{III}{IV}\end}
\def\testA #1{\ifx\end#1\relax\else
   \advance\tmpnum by1
   \param\the\tmpnum:\ifx^#1^\param \else#1\fi \space
   \expandafter\testA \fi
}

\test{A}

\bye

wipet
  • 74,238
  • You failed to notice that II, III and IV are optional argument defaults. How would you cope with them? – egreg Mar 14 '22 at 12:01
4

When TeX absorbs the replacement text for a macro it doesn't interpret tokens in any way. Using \edef is out of question either, because assignments are not performed in an \edef replacement text.

You can use an indirect method, though.

\documentclass{article}

\ExplSyntaxOn

% we need a variant \cs_generate_variant:Nn \cs_new:Nn { NV }

% the template: first argument, index, colon, argument corresponding to index \cs_new:Nn __alvaro_aux:n { ##1#1:###1~ }

% set a tl to iterate the desired template \exp_args:NNe \tl_set:Nn \l_tmpa_tl { \int_step_function:nN { 4 } __alvaro_aux:n }

% define an internal function; \use:n is needed to reduce the number of # \use:n { \cs_new:NV \alvaro_test:nnnn \l_tmpa_tl }

% the external function \NewDocumentCommand{\test}{mO{II}O{III}O{IV}} { \alvaro_test:nnnn { #1 } { #2 } { #3 } { #4 } }

\ExplSyntaxOff

\begin{document}

\test{A}

\test{B}[1]

\test{C}[1][2]

\test{D}[1][2][3]

\end{document}

Well, it was interesting, because you can see ###1 (three # characters are not that common).

enter image description here

egreg
  • 1,121,712
2

you can try for expl3

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand\test{m O{II} O{III} O{IV}}{
\clist_set:Nn \l_tmpa_clist{#1,#2,#3,#4}
\int_step_inline:nn{4}
{
    #1##1: \clist_item:Nn \l_tmpa_clist{##1}\quad
}
}
\ExplSyntaxOff
\begin{document}

\test{A}\par \test{A}[B][C][D]

\end{document}

enter image description here

ljguo
  • 1,213
  • 1
  • 4
  • 12
2

As long as you don't specify why you wish iteration I suggest to do without iteration:

\documentclass{article}

\NewDocumentCommand\test{m O{II} O{III} O{IV}}{% #11:#1\quad#12:#2\quad#13:#3\quad#14:#4% }

\begin{document}

\test{A}

\test{B}[1]

\test{C}[1][2]

\test{D}[1][2][3]

\end{document}

enter image description here


Alternatively you can have a loop for processing a list of default-values, in each iteration grabbing an optional argument and in case its value equals the -NoValue--marker delivering the default-value coming from the list and otherwise delivering that optional argument:

\documentclass{article}

\makeatletter \NewDocumentCommand\test{m}{% #11:#1\testloop{2}{#1}{II}{III}{IV}%<- here you can append more default-values if you wish TeX to process more optional arguments. {}{}\relax }% @ifdefinable\testloop{% \long\def\testloop#1#2#3#4\relax{% \ifcat$\detokenize{#4}$\expandafter@gobble\else\expandafter@firstofone\fi {\testloopopt{#1}{#2}{#3}{#4}}% }% }% \NewDocumentCommand\testloopopt{mmmmo}{% \quad#2#1:\IfNoValueTF{#5}{#3}{#5}% \expandafter\testloop\expandafter{\number\numexpr#1+1\relax}{#2}#4\relax }% \makeatother

\begin{document}

\test{A}

\test{B}[1]

\test{C}[1][2]

\test{D}[1][2][3]

\end{document}

enter image description here

Ulrich Diez
  • 28,770