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.

... because I don't know why this works

\def\my@addextraoption#1{%
    \xdef\my@extraoptions{%
        \@ifundefined{my@extraoptions}\@empty\my@extraoptions,%
         \zap@space#1 \@empty}}
\DeclareOption*{\my@addextraoption\CurrentOption}

but this doesn't:

\DeclareOption*{%
    \xdef\my@extraoptions{%
        \@ifundefined{my@extraoptions}\@empty\my@extraoptions,%
         \zap@space\CurrentOption \@empty}}

The latter gives me this error:

! Argument of \zap@space has an extra }.
<inserted text> 
                \par 
l.110 \ProcessOptions\relax

So what gives here? I tend to enable my cargo cult programmer bit when I get that inscrutable Argument of \@foo has an extra }. message (or some other inscrutable message coming deep from the core of TeX/LaTeX). Eventually I find some syntax that appears to work, and then I test it to death to make sure it really does work.

There has to be a better way than enabling my cargo cult programmer bit. TeX/LaTeX is one of the very few languages that triggers that bit inside me. TeX/LaTeX ranks right up there with JCL (yes, that does date me, doesn't it) and postscript/forth in that regard.

share|improve this question
    
Can you add a minimal (non-working), what you posted works for me with no errors, so my first thoughts you are getting these errors from some redefinitions in a package. –  Yiannis Lazarides Jun 6 '11 at 18:33
    
@Yiannis: Odd, I got errors as reported. –  Loop Space Jun 6 '11 at 18:47
    
The title does not give useful information. –  xport Jun 7 '11 at 16:54
1  
@xport: Google the term. Most programmers are cargo cult programmers. They cut and paste dead code because buried in that dead code is some live code that does solve the problem, then they add even more stuff to that already bloated code, and to top it off they wave a dead chicken to eventually 'solve' the problem. LaTeX has this nasty tendency to bring out the cargo cult programmer in even the best of us. –  David Hammen Jun 8 '11 at 21:45

2 Answers 2

up vote 10 down vote accepted

It's to do with when TeX swallows spaces. There's actually two parts to the "when". The first thing to know is that TeX swallows spaces when it reads the input. The second thing to know is that TeX swallows spaces after a command name. Putting those two together, we see that TeX swallows a space when it follows a command name in the input but not when already-read code is reinserted in to the stream. Let's now examine the two cases:

  1. \zap@space#1 \@empty When TeX reads this, it doesn't know that #1 is going to be a command. For all it knows, #1 could be a pile of fetid dingo's kidneys. So it doesn't swallow the space. Thus when this code is executed, TeX sees: \zap@space\CurrentOption \@empty with the space still there. Hence, everyone is happy.

  2. \zap@space\CurrentOption \@empty When TeX reads this, it knows that \CurrentOption is a command, so it swallows the space. Thus the actual code that the processor sees is \zap@space\CurrentOption\@empty and there's no space to zap, so \zap@space complains.

There are probably many solutions. A simple one is to ensure that when TeX sees that space then it isn't in "space swallowing" mode. To do this, we have to figure out a way to make it so that the last thing that TeX saw before the space isn't a command. A common way to do this is to put an empty group there: \zap@space\CurrentOption{} \@empty works.

(Edit:) As Philippe points out, the above adds the brace to the argument of \my@extraoptions. Here's one that doesn't, with fewer \expandafters:

\expandafter\zap@space\expandafter\CurrentOption\space\@empty

but in fact, looking at the output of \my@extraoptions then I think that there is something wrong with your \@ifundefined bit. Here's what I think you are after:

\ProvidesPackage{test}

\DeclareOption*{%
  \edef\@temp{%
    \xdef\noexpand\my@extraoptions{%
    \@ifundefined{my@extraoptions}{}{\my@extraoptions,}%
    \noexpand\zap@space\CurrentOption\space\noexpand\@empty}}
  \@temp
}

\ProcessOptions

\show\my@extraoptions

with a calling program:

\documentclass{article}
\usepackage[hello,world]{test}

\begin{document}
hello
\end{document}

In the log file, I get that \my@extraoptions is hello,world. With your original code, I got ,hello,world - that is, with an initial comma.

As well as correcting the \@ifundefined, I've taken the alternative route through expansion. Rather than an excess of \expandafters, I've used an \edef to define a temporary macro which expands stuff as necessary (with a few expansions inhibited with \noexpand) which is then called straight after to do the actual definition.

(This is a trick I learnt here ... When to use \edef, \noexpand, and \expandafter? ... after seeing a macro with about 20 \expandafters be condensed to just one. That's also where I was accused of being a Cargo Cult Programmer!)

share|improve this answer
5  
PS I'm unashamedly a Cargo Cult Programmer myself. One reason I like this place is that it's a gentle way of finding out more. –  Loop Space Jun 6 '11 at 18:32
    
Using \space is indeed better than \@iden. –  Philippe Goutet Jun 6 '11 at 18:57
    
Thanks, both of you. One remaining question: You don't have a noexpand in front of the \xdef. Since this is in an \edef, it looks like your solution will refine \noexpand -- but it obviously doesn't. Putting a \noexpand in front of the \xdef also works. That \noexpand\xdef is apparently a gratuitous use of noexpand, but I'm a bit queasy without it. –  David Hammen Jun 6 '11 at 20:58
    
@David: The token \xdef is not expandable, so the expansion operator ignores it. One could say that \xdef comes with a \noexpand already built in. When the macro is called, then it gets processed. You could try putting \show\@temp with and without the \noexpand in front of the \xdef (and try removing a few other \noexpands just to see what effect they have). –  Loop Space Jun 6 '11 at 21:13
    
I know what those other \noexpands do. I presume this non-expandability pertains only to TeX primitives, and only to some of them. All members of the def family, I assume, what else is unexpandable? It seems to me that a lot of this cargo cult stuff could be avoided if TeX supplied an \expand primitive. Is there a way to force an expansion, sans things like this edef trick? –  David Hammen Jun 6 '11 at 21:34

The definition of \zap@space is

\def\zap@space#1 #2{%
  #1%
  \ifx#2\@empty\else\expandafter\zap@space\fi
  #2}

This means that \zap@space kills spaces one by one, but needs to end with a space followed by \@empty. When you write \zap@space#1 \@empty, this will be the case because #1 is replaced by its content. But when you write \zap@space\CurrentOption \@empty, TeX eats the space after \CurrentOption (as it always does for spaces after multi-letter macros), and so it's equivalent to \zap@space\CurrentOption\@empty which is incorrect as there's no space before \@empty.

I cannot be sure without a complete example of your code, but I think this will work in your case:

\expandafter\expandafter\expandafter\zap@space\expandafter\@iden\expandafter{\CurrentOption} \@empty

The \@iden macro expands to give its argument (the definition is \def\@iden#1{#1}) so \@iden{\CurrentOption} will be the same as \CurrentOption, but because of the ending brace, the space after it is kept. But as you need to expand \@iden, you have to use \expandafter. For the spaces of \CurrentOption to be trimmed, you also need an additionnal two \expandafter before (you won't get an error if you omit them, but no spaces will be zapped).

Added after seeing Andrew's answer: the advantage of this compared to what Andrew suggested (\zap@space\CurrentOption{} \@empty) is that no spurious braces are added to the result.

share|improve this answer
    
Good point about the braces! I've edited mine to provide an alternative without them. –  Loop Space Jun 6 '11 at 18:46

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.