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
2
  • What exactly do you want to do, maybe there is another way?
    – ljguo
    Mar 14, 2022 at 8:49
  • IIRC TeX does not expand tokens when looking for a number following #, so this cannot really work. See e.g. 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, 2022 at 9:12

4 Answers 4

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
1
  • You failed to notice that II, III and IV are optional argument defaults. How would you cope with them?
    – egreg
    Mar 14, 2022 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

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

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .