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.

It is easy to forget a \relax or % at the line end of commands in the body of macros. For example, in writing

\def\myMacro{
  \invokeX
  \invokeY{}
  \invokeZ
}

You must add a space swallower at the end of the first and third line, but not at the end of the second, fourth and fifth lines.

A simple global search/replace of {$ by {% will fix omissions such as that of the first line, and is not likely to add redundant swallowers. But, a global search/replace of }$ by }% will also add a percent after the fifth line.

For the benefit of Google search visitors, let me write the vi commands for these searches:

:%s/{$/{%/
:%s/}$/}%/

But as I mention, these are not perfect, and may not take care of other tricky cases.

So, my question is: Can you do better? Perhaps there is a clever redefinition of \def that would eliminate the need for these ugly additions, or perhaps there is some other automatic mechanism that would warn against such potential bugs?

share|improve this question
1  
You should mentioned that $ is the end-of-line marker in regular expressions used for the search&replace. Otherwise some people will wonder what you mean with replacing {$ with {%. –  Martin Scharrer Feb 27 '11 at 19:58
    
I edited your question, because your original example with \invokeM1 is equivalent to \invokeM 1 (digits cannot be part of the macro name (with the default catcodes)) and therefore all lines would need to eat spaces. –  Lev Bishop Feb 27 '11 at 20:14

3 Answers 3

up vote 2 down vote accepted

I wrote some code to check a chunk of code. It counts how many spaces and how many spaces+end-line-characters appear in the argument of \cnel, and displays it to the terminal. It also runs the code: this way, it shouldn't be too disruptive.

% Should print 1 and 4.
\cnel{
  \def\foo{b ar
  }
}

% Just to stop the run (press <enter>)
\read-1 to\dummy

% Should print 0 and 0, because there is no space nor end-of-line.
\cnel{%
  \show\foo
}

The trick is to

  • use \scantokens to read the argument with various catcode setups for the end-of-line character: either ignored or space. We first need to read it with catcode other because "ignored" it would disappear, and "space", it would become indistinguishable from a true space.

  • use \lowercase to convert spaces to some other character (namely ^^S), then \detokenize, and search for the string ^^S. (That string is itself produced by detokenizing a lowercase space.)

.

% `cnel` = `check if no end-line`.
%
\catcode`\@=11\relax
\newcount\cnel@count


\def\cnel{%
  \begingroup%
  \catcode\endlinechar=12\relax%
  \cnel@aux%
}
\newcommand{\cnel@aux}[2][\^^S]{%
  % Set spaces to lowercase to the first, optional, argument (default ^^S).
  \lccode`\ `#1%
  % First we count the number of spaces with a setting in which 
  % the end-line character is ignored. Then we count it after setting
  % it to be a space instead.
  \typeout{}%
  \typeout{First, we ignore end-of-line characters.}%
  \catcode\endlinechar=9\relax%
  \scantokens{\cnel@count@spaces{#2}}%
  \typeout{}%
  \typeout{Then, they are counted as spaces.}%
  \catcode\endlinechar=10\relax%
  \scantokens{\cnel@count@spaces{#2}}%
  % To execute the code, we keep this endline=space setting.
  \scantokens{\gdef\cnel@gtmp{#2}}%
  \endgroup%
  \cnel@gtmp%
}

\newcommand{\cnel@count@spaces}[1]{%
  \cnel@count0\relax%
  \lowercase{\expandafter\cnel@w\detokenize{. .#1 }\relax%
    \typeout{\unexpanded{#1}}%
  }%
  \typeout{Number of space(s): \the\cnel@count.}%
}
\def\cnel@w.#1.{%
  \def\cnel@w@aux##1#1##2\relax{%
    \ifempty@nTF{##2}{}{%
      \advance \cnel@count by 1\relax%
      \cnel@w@aux##2\relax%
    }%
  }%
  \cnel@w@aux%
}
\def\ifempty@nTF#1{%
  \expandafter\ifx\expandafter a\detokenize{#1}a%
  \expandafter\@firstoftwo%
  \else%
  \expandafter\@secondoftwo%
  \fi%
}
share|improve this answer
    
Brilliant! I just cannot tell you how much I appreciate this. I think this should be packaged and advertised! –  Yossi Gil Mar 12 '11 at 10:30
    
@Yossi: I'll try to understand the code again this or next week-end, and perhaps turn it into an environment, and think a bit more about catcodes. –  Bruno Le Floch Mar 12 '11 at 16:02
    
@Bruno: I know this may be very time consuming. Should you decide to to do this, I trust the community will be very grateful. –  Yossi Gil Mar 13 '11 at 13:50
    
@Yossi: it's not that time consuming. I just can't figure out a good interface. And I can't remember why I treated spaces and end nf line character differently above. If you have any clue, I'd appreciate the help. –  Bruno Le Floch Mar 17 '11 at 10:09
    
@Yossi: I'm getting into unforseenly busy times, unfortunately. And my quick attempt at packaging tonight didn't work. So that will happen later. Sorry. --- About why eol are treated specially: the idea is that the user might want to ignore spaces which appear in the middle of the line, because those are more often intentional. Also I need to implement ignoring spaces outside braces. –  Bruno Le Floch Mar 18 '11 at 23:55

ConTeXt provides \starttexdefinition ... \stoptexdefinition environment that does exactly that. Using that you can write your command as:

\starttexdefinition myMacro
  \invokeX
  \invokeY{}
  \invokeZ
\stoptexdefinition

I believe that LaTeX3 also provides a similar syntax.

share|improve this answer
    
Thanks Atidya. I applied Lev's correction to your answer as well. –  Yossi Gil Feb 28 '11 at 3:51

For LaTeX, using the expl3 package:

\ExplSyntaxOn
\def\myMacro{
  \invokeX
  \invokeY{}
  \invokeZ
}
\ExplSyntaxOff

or even

\ExplSyntaxOn
\def \myMacro {
  \invokeX
  \invokeY { }
  \invokeZ
}
\ExplSyntaxOff
share|improve this answer
4  
You could add that when the ExplSyntax is on, spaces are ignored, the end of line character is a space, ~ is a genuine space, and : and _ are letters (usable in macro names). (Am I missing something?) –  Bruno Le Floch Feb 28 '11 at 10:58

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.