4

I'm trying to build some macros for parsing arguments of other macros in which I'm smuggling in hidden arguments.

The following code works perfectly:

 \documentclass{article}
 \makeatletter

\def\ae@w@@{1}
\def\ae@m@@{1}

\def\ae@set@m@@#1/#2\ae@sep@@{\def\ae@m@@{#1}}

\def\ae@sep@wm@@#1/#2\ae@sep@@{%%
  \def\ae@w@@{#1}%%
  \ifx#2\relax\else
    \ae@set@m@@#2\ae@sep@@
  \fi
}

\def\aegetWM#1{%%
  \bgroup
    \ae@sep@wm@@#1/\relax\ae@sep@@
    W=\ae@w@@\newline
    M=\ae@m@@
  \egroup
}

\makeatother
%% to visually emphasize groupings
\setlength{\parskip}{5ex}
\setlength{\parindent}{0pt}
\pagestyle{empty}
\begin{document}

\aegetWM{400}

\aegetWM{234/100}

\aegetWM{300}

\end{document}

enter image description here

But if I defined \ae@set@m@@ as follows:

\def\ae@set@m@@#1/\ae@sep@@{\def\ae@m@@{#1}}

I get the following error:

Runaway argument?
100/\relax \ae@sep@@ \fi W=\ae@w@@ \newline M=\ae@m@@ \egroup  
! Paragraph ended before \ae@set@m@@ was complete.
<to be read again> 
                   \par 
l.35 

? 

What's going on here? Why does one of these definitions work and the other not?

  • With the second definition, \ae@set@m@@ requires /\ae@sep@@, while it finds /\relax\ae@sep@@. – egreg Oct 14 '13 at 23:11
  • also \ifx#2\relax\else should probably be (at least) \ifx\relax#2\else for safety otherwise it's true if the first two tokens in #2 are the same rather than if #2 is \relax – David Carlisle Oct 14 '13 at 23:20
  • @egreg Thank you. I wasn't noticing that I had \relax in there. – A.Ellett Oct 14 '13 at 23:26
  • @DavidCarlisle Thank you. I hadn't thought about that possibility. Duly changed. :) – A.Ellett Oct 14 '13 at 23:27
  • You may need further precautions if 2 can be empty, otherwise you are doing \ifx\relax\else (see egreg's version:-) – David Carlisle Oct 14 '13 at 23:32
6

When you say

\def\cs#1/\@nil{...}

each call of \cs must find /\@nil somewhere before the end of the paragraph, because the macro is not \long, or, if you declare it \long, before the end of the file currently being read.

With your second definition, you call \ae@set@m@@#1/\relax\ae@sep@@ and TeX doesn't find the required tokens.

Actually, you're doing it in a too complicated way:

\documentclass{article}
\makeatletter
\def\aegetWM#1{\ae@sep@wm@@#1//\ae@sep@@}
\def\ae@sep@wm@@#1/#2/#3\ae@sep@@{%
  W=#1\newline
  M=\if\relax\detokenize{#2}\relax1\else#2\fi
}
\makeatother
%% to visually emphasize groupings
\setlength{\parskip}{5ex}
\setlength{\parindent}{0pt}
\pagestyle{empty}
\begin{document}

\aegetWM{400}

\aegetWM{234/100}

\aegetWM{300}

\end{document}

Let's look what happens when you call \aegetWM{400}:

\ae@sep@@wm@@ 400//\@ae@sep@@

so #1<-400, #2<-<empty>, #3<-<empty>. When you call \aegetWM{234/100} we get

\ae@sep@@wm@@ 234/100//\@ae@sep@@

so #1<-234, #2<-100, #3<-/.

  • So \detokenize{#2} resolves to \relax if the second argument is empty? In other words, \detokenize{}=\relax? – A.Ellett Oct 14 '13 at 23:39
  • @A.Ellett No, \detokenize{} produces nothing at all. If the second argument is empty you get \if\relax\relax which is true. Otherwise you get \if\relax<first char in the argument (detokenized)> which is surely false. – egreg Oct 14 '13 at 23:42
  • So I'm a bit confused by the comments from you and David to my original post about the bad programming. Isn't it the same either way? I'd prefer not to program badly, but I'm not seeing where the difference is here. – A.Ellett Oct 14 '13 at 23:44
  • @A.Ellett \ifx\relax\else or \if\relax\else is bad programming. With my code you can't get something like that: it's \if\relax\detokenize{#2}\relax<T>\else<F>\fi; even if <T> is empty you can't get the "bad guy". – egreg Oct 14 '13 at 23:46
  • Now I see. Thanks for the recommendations. – A.Ellett Oct 14 '13 at 23:48

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.