summaryrefslogtreecommitdiff
path: root/info/challenges/aro-bend/answer.002
blob: 9cdb8ff78af9d05fc1bca50c156c4191bc61df93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
[Posted to info-tex on 25 Oct 91; see answer.001]
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
"*** Exercise 2 (hard):
"Define an "ifempty" macro that takes one argument and resolves
"essentially to \iftrue if the argument is empty, and \iffalse
"otherwise. This is useful for handling arguments given by
"users to commands defined in a macro package such as LaTeX.
"
"Plain TeX or LaTeX-style solutions are both acceptable, that
"is,
"
"     \ifempty{...}TRUE CASE\else FALSE CASE\fi
"
"or
"
"     \ifempty{...}{TRUE CASE}{FALSE CASE}

The LaTeX-style solution that I had prepared was, I thought, pretty
good, but Donald Arseneau observed that it fails the test
\test{{\iftrue a\else b\fi}}, which was not in my list of tests.

>>Solution 1 [mine]
\catcode`\@=11
%    \@car is actually already defined in latex.tex, but for
%    maximum robustness it needs to have the \long prefix:
\long\def\@car#1#2\@nil{#1}
\long\def\@first#1#2{#1}
\long\def\@second#1#2{#2}
\long\def\ifempty#1{\expandafter\ifx\@car#1@\@nil @\@empty
  \expandafter\@first\else\expandafter\@second\fi}
\catcode`\@=12

\long\def\test#1{\begingroup \toks0{[#1]}%
  \newlinechar`\/\message{/\the\toks0:
  \ifempty{#1}{EMPTY}{NOT empty}%
}\endgroup}

>>EndSolution

The advantage of using the auxiliary macros \@first and \@second,
together with the \expandafter's, is that it allows the true and/or
false cases to end with arbitrary things, even macros that require
arguments that have not yet been read (any number of arguments, even
delimited arguments).

From here it is easy to implement an \ifnotempty test that has a
null false case. This is often useful in dealing with user-supplied
arguments: `If #1 is empty, do nothing; otherwise, do the following
with #1: ...'

\long\def\ifnotempty#1{\ifempty{#1}{}}

Don Arseneau came up with a plain TeX style solution, using an
ingenious device with \then to pass the test case
\expandafter\iffalse\test{x}\fi. The comments in the
solution are his.

>>Solution 1 [Donald Arseneau]:
% \ifblank{...}\then Test if a parameter is blank (null or spaces).
% Use the inaccessable "letter" @ to separate parameters. The two cases are:
%  _text_is_not_blank_             _text_is_blank_
%  #1<- whatever                   #1<-@
%  #2<- whatever (possibly null)   #2<-
%  #3<- @                          #3<-.
%  #4<- ..                         #4<-.
%  \if @.. {false}                 \if .. {true}
%  In the {false} case, the extra period is skipped so it doesn't hurt.

\catcode`\@=11 % as in plain.tex
\let\then\iftrue
\long\def\ifblank#1\then{\Ifbl@nk#1@@..\then}%
\long\def\Ifbl@nk#1#2@#3#4\then{\if#3#4}
\catcode`\@=12

\long\def\test#1{\begingroup \toks0{[#1]}%
  \newlinechar`\/\message{/\the\toks0:
  \ifblank{#1}\then EMPTY\else NOT empty\fi%
}\endgroup}

>>EndSolution

The good thing about this solution is that it doesn't subject any part
of the user-supplied argument to the \ifx test. Using @ with category
code of 11 as a delimiter for the user-supplied text is extremely safe
because even in internal code @ doesn't appear by itself, only as part
of control sequence names. In a partial solution, Peter Schmitt pushed
the idea a little further by using space with category code 3 as the
delimiter.

There is another way of handling the problematic \iffalse test, in a
plain-TeX style solution, by using a suggestion of Donald Knuth that
appeared in TeXhax a while ago, in reply to a query of Stephan von
Bechtolsheim (texhax89, #38 (post from svb, 17 Apr 89)).

>>Solution 3 [Arseneau/Knuth]:
% Usage: \if\blank{#1}...\else...\fi

\catcode`\@=11 % as in plain.tex
\long\def\blank#1{\bl@nk#1@@..\bl@nk}%
\long\def\bl@nk#1#2@#3#4\bl@nk{#3#4}
\catcode`\@=12

\long\def\test#1{\begingroup \toks0{[#1]}%
  \newlinechar`\/\message{/\the\toks0:
  \if\blank{#1}EMPTY\else NOT empty\fi%
}\endgroup}

>>EndSolution

At the end of Exercise 2 I wrote:

"The two tests on the first line should produce a message "EMPTY" and
"the remaining ones, "NOT empty". The reason for saying that the second
"test should return "EMPTY" is that (1) this is the ideal behavior for
"the applications I've encountered so far; (2) at least one other person
"working independently arrived before me at a solution essentially
"identical to mine, including this behavior. The details and credit to
"the other guy will be given at solution time.

The name of the `other guy' is Michael Wester; a listing of his macros
was published in the preprints for the July 1991 TUG meeting in Dedham,
Massachusetts (`Form Letter in LaTeX with 3-across Mailing Labels
Capability', joint paper with Jackie Damrau). In rereading the preprint
recently, it seems to me the presentation is more different from
Exercise 2 and its solutions than I had previously imagined, but the
essential ideas are there. See \wcar, \wcdr and related macros.

By the way, if anyone came up with a fully expandable test (suitable
for use inside a \message) for which \test{ } came up false instead of
true, I would be interested to hear about it. I didn't mean to
eliminate that possibility in my original statement of the problem.