6

My question is about reading and using the arguments of a custom created command using \def.

Let's say I have the command \mytestcommand and I want to read and use it's arguments (optional and mandatory of unknown number)

I have the code:

\documentclass[a4paper,10pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}

\newcounter{NumOptionalArgFound}
\newcounter{NumMandatoryArgFound}
\xdef\MaxMandArgs{3}


\makeatletter
\def\SearchForOptional{%
   \@ifnextchar[%
   {\FoundOptional}
   {\relax}
}
\def\FoundOptional[#1]{%
   \stepcounter{NumOptionalArgFound}%
   \xdef\curOptArg{#1}%
   \global\expandafter\let\csname OptionalArgument\arabic{NumOptionalArgFound}\endcsname\curOptArg%
   \@ifnextchar[%
   {\FoundOptional}
   {\SearchForMandatory}
}
\def\SearchForMandatory{%
   \@ifnextchar\bgroup%
   {\CheckIfMandatory}
   {\relax}
}
\def\CheckIfMandatory#1{%
   \ifnum\pdf@strcmp{#1}{\bgroup}=0%
      #1%
   \else
      \ifnum\arabic{NumMandatoryArgFound}<\MaxMandArgs%
         \stepcounter{NumMandatoryArgFound}%
         \xdef\curManArg{#1}%
         \global\expandafter\let\csname MandatoryArgument\arabic{NumMandatoryArgFound}\endcsname\curManArg%
         \expandafter\expandafter\expandafter\SearchForMandatory%
      \else
         #1%
      \fi
   \fi
}
\makeatother

\def\mytestcommand{
\xdef\MaxMandArgs{2}
\SearchForOptional
}


\def\printArgs{
\foreach \i in {1,...,\theNumOptionalArgFound}{
Opt Arg \i: \csname OptionalArgument\i\endcsname\par
}

\foreach \i in {1,...,\theNumMandatoryArgFound}{
Mand Arg \i: \csname MandatoryArgument\i\endcsname\par
}
}


\begin{document}

\mytestcommand[Optional 1][Optional Second]{first Mand}{second Mand}{thirdMand}\bgroup noMand \egroup

\printArgs
\end{document}

This code works fine and finds-saves the optional and mandatory arguments of ot. My problem is that if i try to use one of the counters or of the arguments inside the definition of \mytestcommand it fails to find the number or the argument.

More specific, I would like to use \printArgs command inside of \mytestcommand definition... (So I can know I am able to use the arguments inside my command definition).

The problem appears if I change the definition of \mytestcommand to:

\def\mytestcommand{
\xdef\MaxMandArgs{2}
\SearchForOptional
\printArgs
}

Thank's=Thanks

PS: I am looking for a LaTeX2e solution, but LaTeX3 is probably helpful for other users... I would like not to use packages that I know exist for such jobs but if possible, "primary" LaTeX commands like mine...

  • 1
    Just curious: why not L3? – cfr Jul 28 '17 at 1:40
  • @cfr I am going to use these commamds in a book about LaTeX2e... If later I manage to learn some more LaTeX3 and be able to write a LaTeX3 book too, I will appreciate the answers in LaTeX3... but for now I am looking for LaTeX2e solutions and if possible without usage of extra packages (when possible) – koleygr Jul 28 '17 at 1:46
  • 1
    You are against packages, but you're happy to load TikZ, of all things? – cfr Jul 28 '17 at 1:47
  • 1
    My point about \def was just that you have already given up on a 2e approach. That's perfectly sensible, but then you are going the TeX route as opposed to the LaTeX one offered by L3. That will generally mean less readable code and the need to take much more responsibility for checks. And spurious spaces, of course. – cfr Jul 28 '17 at 4:08
  • 4
    I'd urge you not to have document commands with this type of syntax: experience strongly suggests the signature of commands should be well-defined. If you need variable lists of arguments, use comma lists, keyval syntax, etc. – Joseph Wright Jul 28 '17 at 8:22
6

Add an x to see the problem:

\def\SearchForOptimal{%
   \@ifnextchar[%
   {\FoundOptimal}%
   {x\relax}%
}

'x' marks the spot

The next character in this case is \ and not [. Hence, the 'if false' condition is used, x\relax.

Note that the problem has nothing to do with the use of the arguments. Try

\def\mytestcommand{%
\xdef\MaxMandArgs{2}%
\SearchForOptimal
x
}

for comparison. The way you have \SearchForOptimal written, it has to be the last thing in \mytestcommand.

  • Thanks @cfr this explains exactly my problem... Of course doesn't solve it, but I don't think there is an easy solution... I will try today to find a solution and will come back to accept your answer if I will not find a real solution... Probably my way is much more difficult than I expected.... – koleygr Jul 28 '17 at 4:30
  • Thanks... your answer gave me the answer to what was wrong and have fixed that. Also thanks for the discussions about L3, keyval and xparse etc – koleygr Aug 27 '17 at 3:01
5

Something like this.

\documentclass[a4paper,10pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}

\newcounter{NumOptionalArgFound}
\newcounter{NumMandatoryArgFound}

\newcommand\MaxMandArgs{3}% default value

\makeatletter

\newcommand\SearchForArguments {%
   \setcounter{NumOptionalArgFound}{0}%
   \setcounter{NumMandatoryArgFound}{0}%
   \@SearchForOptional
}

% fetching optional arguments

\newcommand\@SearchForOptional{%
    \kernel@ifnextchar[\@FoundOptional\@SearchForMandatory
}

\def\@FoundOptional[#1]{%
   \stepcounter{NumOptionalArgFound}%
   \global\@namedef{OptionalArgument\arabic{NumOptionalArgFound}}{#1}%
   \@SearchForOptional
}

% fetching mandatory arguments

% I do not quite understand this \bgroup business but aiming
% to be close to OP. It actually complicates things a lot because
% one can not use a \futurelet to distinguish \bgroup from {

\newcommand\@SearchForMandatory[1]{%
    \edef\@tempKola{\detokenize{#1}}%
    \edef\@tempKolb{\detokenize{\bgroup}}%
    \ifx\@tempKola\@tempKolb
      \expandafter\@firstofone
    \else
      \ifnum\value{NumMandatoryArgFound}<\MaxMandArgs %<- must leave a space
       \stepcounter{NumMandatoryArgFound}%
       \expandafter\expandafter\expandafter\@GetMandatoryAndGoOn
      \else
       \expandafter\expandafter\expandafter\@firstofone
      \fi
    \fi {#1}%
}

\newcommand\@GetMandatoryAndGoOn[1]{%
    \global\@namedef{MandatoryArgument\arabic{NumMandatoryArgFound}}{#1}%
    \@SearchForMandatory
}
\makeatother

\newcommand\printArgs{{\color{blue}%
Value of MaxMandArgs: \MaxMandArgs

\ifnum\value{NumOptionalArgFound}>0
  \foreach \i in {1,...,\theNumOptionalArgFound}{%
  Opt Arg \i: \csname OptionalArgument\i\endcsname\par
}\fi

\ifnum\value{NumMandatoryArgFound}>0
  \foreach \i in {1,...,\theNumMandatoryArgFound}{%
  Mand Arg \i: \csname MandatoryArgument\i\endcsname\par
}\fi
}}

\newcommand\mytestcommand[1]{\gdef\MaxMandArgs{#1}\SearchForArguments}

\begin{document}

\mytestcommand{2}[Optional 1][Optional Second]{first Mand}{second Mand}{third Mand}\bgroup noMand \egroup

\printArgs

\mytestcommand{3}[O1][O2][O3][O4]{M1}{M2}{M3}{M4}{M5}\bgroup STOP\egroup

\printArgs

\end{document}

enter image description here

What is in black is what has not been considered part of the thing.

One could modify the macros above to not swallow the arguments it identifies as either optional or mandatory.

There is a powerful package called xparse which seems indicated here.

And with

\mytestcommand{6}[O1][O2][O3][O4]{M1}{M2}{M3}{M4}{M5}\bgroup STOP\egroup

\printArgs

enter image description here

  • 2
    \bgroup is not likely to be used by casual LaTeX user. It is important for \vbox or \toks things in various contexts. – user4686 Jul 28 '17 at 9:00
  • 2
    \bgroup...\egroup would never be an argument of a TeX macro. Only the first token \bgroup will be fetched (and subsequent ones possibly) – user4686 Jul 28 '17 at 9:02
  • 1
    You can use an approach like the above to fetch arbitrarily many optional arguments, then just use a \newcommand\GetMandatories[3]{...} for example if the macro will have 3 mandatory arguments. Having a non-prescribed in advance number of mandatory arguments looks self-contradictory ... or then you need some definitive end-marker, but do not use \bgroup for that. – user4686 Jul 28 '17 at 9:09
  • 1
    However I would certainly go with @JosephWright comment that if the number of optional arguments is not fixed, it is much better to use some other approach, e.g. key-value syntax. – user4686 Jul 28 '17 at 9:14
  • 1
    I think you should look at xparse package. – user4686 Jul 28 '17 at 9:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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