[The four parts of this answer were originally posted separately, as indicated in the subject lines.] Date: 16 Dec 1993 16:34:45 -0500 (EST) From: Michael Downes Subject: Around the Bend #15, answers To: info-tex@shsu.edu Exercise 15 asked for a function \trimspace to trim a trailing space from the replacement text of a macro, and a function \trimspaces to trim both a leading and a trailing space. At the time of posting the exercise I had no prepared solution; as luck would have it the problem was rife with latent complications (including some hard questions about limiting the domain of application), which propagated an unusually diverse crop of approaches among the submitted solutions, and which made the task of preparing a good summary extraordinarily difficult. Even after breaking down the `summary' into two or three pieces, to avoid a too formidably large monolith of a posting, I'll have to leave out some material that I would otherwise have included. I'd say Donald Arseneau deserves credit for the best analysis, including an accurate survey of brace-stripping problems. Nearly everyone, including myself, had missed a lurking flaw of that kind in the first submitted version of their solution. Another good idea of Donald's that caught my fancy was to use TeX's built-in scanning procedures for to strip the leading space in \trimspaces. I managed to work that into my own best solution, much to my satisfaction. Peter Schmitt came up with perhaps the most aerodynamic solution, on his second go-round. A solution by Ian Collier differed notably from the others by using \meaning to look for a leading space. Another submission, from Gary McGary, contained some original syntactic ideas, and explored the more general problem of removing an arbitrary token pattern at the end of a token list. A careless, off-the-cuff remark of mine in the statement of Exercise 15 that after removing a leading space, \trimspaces should call \trimspace to remove a trailing space, was probably a mistake. In most cases, at least, \trimspaces can be more elegantly written by letting the two different space-removal procedures share a few tokens at a lower level. From Donald's analysis: > When I first read the question, I thought `why isn't there an answer > with the question, because that one is easy?' As I started to type > my answer `cold', I realized that what I had used previously to > ignore leading spaces ("\def\something#1#2\weird{#1#2}") had the bad > side-effect of stripping braces if the parameter began with "{". I append below Peter Schmitt's solution, more or less as he wrote it. The commentary refers to earlier correspondence in a place or two but I believe there is sufficient context to make everything intelligible. Test #5 in the test suite traps the insidious brace-stripping problem that infested most of the solutions in their first incarnation. More on Exercise 15 to follow, some time in the next few days. Michael Downes, mjd@math.ams.org %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% >>Solution 1 (Peter Schmitt, a8131dal@awiuni11.edvz.univie.ac.at) Since I wanted to stay with delimited arguments it was clear that one has to add a token (or tokens) in order to hide braces, which finally have to be removed again. First I came up with using \empty, as you did, but then I switched to a not expandable token because this can more efficiently be used as a parameter delimiter. \trimspaces and \trimspace are just used to expand the argument and add delimiting tokens in front and at the end of it, and set up the delimiting tokens for \Trimspace and \Trimspaces, too. As Donald does, I do not call \trimspace by \trimspaces but rather \Trimspace by \Trimspaces. It would be easy to offer \TrimLeft \TrimRight and \TrimBoth and also \TrimLeftS \TrimRightS and \TrimBothS which iterate in the (very unlikely!) case that there are several consecutive space tokens. \Trimspaces and \Trimspace remove leading, respectively trailing, spaces of the argument, but they both leave the delimiting tokens in place. These (and outside tokens) are removed by \TrimSpace in the process of redefining the initial controlsequence. \catcode`\<=3 \catcode`\>=3 \def\trimspace #1{\expandafter\expandafter\expandafter \Trimspace\expandafter <#1> >\\#1} \def\trimspaces #1{\expandafter\expandafter\expandafter \Trimspaces\expandafter <#1>< <\\#1} %% \Trimspaces < text>< <\\ |< text>| ==> %% -> || + |text> + | <| %% => ||+| <|+|text>| == | | %% %% \Trimspaces < <\\ || ==> %% -> || + || + || %% => ||+||+|| == || %% \Trimspace >\\ || ==> %% -> || %% => |\\ == |\\| %% %% \Trimspace >\\ || ==> %% -> || + || %% => ||+>\\ == |>| \def\Trimspaces #1< #2<#3\\{\Trimspace #1#3#2 >\\} \def\Trimspace #1 >#2\\{\TrimSpace #1>\\} \def\TrimSpace #1>#2\\#3{% \expandafter\expandafter\expandafter\expandafter\expandafter \def \expandafter\expandafter\expandafter #3\expandafter {\Remove#1}} \def\Remove#1{} \catcode`\<12 \catcode`\>=12 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \def\Test#1{\def\test{#1}\immediate\write0{|\test|}% \trimspaces\test \immediate\write0{|\test|}% } \let\trim\trimspace \let\trim\trimspaces %%%%%%%%%%%%%%%%%%%%%%%%% \Test{} \Test{ } \Test{ a } \Test{ {}{} } \Test{{braces}} \Test{ {braces} } \Test{ { braces } } \Test{no space and no space} \Test{no space and a space: } \Test{ :a space and no space} \Test{ :a space and a space: } \def\test{ \ifx/ }\trimspace\test\show\test \def\test{ \ifx }\trimspaces\test\show\test \end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Date: 23 Dec 1993 16:21:21 -0500 (EST) From: Michael Downes Subject: Around the Bend #15, answers, 2nd installment To: info-tex@shsu.edu X-ListName: TeX-Related Network Discussion List Some exposition seems called for here in order to lay out various considerations running through my mind and the minds of the other solution-submitters. TRIMMING A TRAILING SPACE There are two possible ways to remove a trailing space. The first one is to step through the given text one token at a time, and construct a new token list in parallel by adding the tokens one by one at the end. If the next token is a space, delay adding it until the subsequent token is checked, and if it turns out the text is exhausted, discard the space instead of adding it. The hard part about this approach is dealing with braces (character tokens with catcode 1 or 2) because a lone brace cannot be passed as a macro argument. A recent posting by \'Eamonn McManus to comp.text.tex on a different sort of problem showed that the braces can indeed be dealt with, it's just not easy. The second, simpler approach is to use TeX's scanning of delimited macro arguments to scan for the ending space and discard it. If you merely scan for a space token, however, you end up scanning through the given text `word' by `word' (word = sequence of non-space characters or brace-delimited groups) instead of token by token, which is perhaps if anything even more awkward than the first method above, since you still must deal with brace complications. The key refinement, therefore, is to scan for a pair of tokens: a space token and some well-chosen bizarre token that can't possibly occur in the scanned text. If you put the bizarre token at the end of the text, and if the text has a trailing space, then TeX's delimiter matching will match at that point and not before, because the earlier occurrences of space don't have the requisite other member of the pair. Next consider the possibility that the trailing space is absent: TeX will keep on scanning ahead for the pair until either it finds them or it decides to give up and signal a `Runaway argument?' error. So you must add a stop pair to catch the runaway argument possibility: a second instance of the bizarre token, preceded by a space. If TeX doesn't find a match at the first bizarre token, it will at the second one. Now all that's left is to test somehow where the hit occurred in order to fork properly. This can be done in various clever ways, as exhibited in the solutions. TRIMMING A LEADING SPACE More analysis from Donald Arseneau: > There are two safe, expandable ways to eat "one optional space": > "\ifnum" using an ascii code "`c" as the second number, and > "\ifdim" using a literal unit of measure like "pt". Oh, yes, > it could also be done with parameter syntax too, but more on > that later. In other words, one way to remove a leading space would be \expandafter\def\expandafter\foo\expandafter{\ifdim0pt=0pt\foo \fi} The \expandafter's would cause the \ifdim to be executed first. Execution of the \ifdim will not terminate until the scanning of the second "0pt" is finished; therefore TeX will start expanding \foo as part of the scanning of the "0pt". Then if a space is the first thing inside the expansion of \foo, it will be removed by TeX as denoting the end of the dimension. Otherwise the first non-space token will terminate the dimension scanning and will be left in place (well, I am glossing over the problem of an expandable token at the beginning of \foo, which can be handled by further refinements). Notice that as written the trailing \fi will be included in the redefinition of \foo. No problem---just rewrite it with the \fi after the closing brace: \expandafter\def\expandafter\foo\expandafter{\ifdim0pt=0pt\foo}\fi [Now for a sharp little question: will that work with \edef instead of \def?: \edef\foo{\ifdim0pt=0pt\foo}\fi. See if you can guess before testing it.] Other ways of removing a leading space include using \futurelet to look at the first token in the scanned text, or using TeX's argument delimiter scanning to scan for a space. The latter method is perhaps most straightforwardly done as a mirror-image of the method for removing a trailing space: make the delimiter , and then call the macro (let's say \trimx) by putting before the scanned text and a stop pair after it, in case a leading space is not present: \trimx#1 \endtrimx It would be possible to do without the bizarre token and have the delimiter consist only of a space, but with some ensuing complications, I think, that would make it scarcely worthwhile. SOME REMARKS ABOUT THE DOMAIN OF THE PROBLEM The application I had in mind was, generally speaking, to remove unwanted spaces at the beginning and end of a piece of text supplied by the user, such as a section title or other heading. Typical situation: A user command \title takes an argument \title{ Some Article Title } with the definition of \title being \def\title#1{\def\savedtitle{#1}\trimspaces\savedtitle} Thereafter we may use \savedtitle in any number of ways: print it; put it in a \mark for running heads; write it to an auxiliary file for table of contents use, or for adding to a BibTeX database; or write it on screen to show progress when typesetting a collection of articles. For the last two examples in particular trimming spaces with \ignorespaces or \unskip is undesirable. Notice also that \unskip will remove *any* trailing glue, including \leaders or explicit \hskip's that might sometimes be added by users for their own inscrutable purposes and whose unexpected removal could be (indeed, has been in true life) the cause of much consternation. If we call \trimspaces in the definition of \title, then leading and trailing spaces are removed once and for all, and none of the many functions that later use \savedtitle need to worry about that task. With this restricted domain of use in mind for \trimspaces, I screened the submitted solutions through the following conditions. Condition 1. The text has been stored in a macro. The result of \trimspaces is a redefinition of the macro. This is not exactly a necessary condition, but removal of this condition would suggest that constructions like \def\foo#1{... \message{Your argument "\trimspaces{#1}" makes me laugh}% ...} should be supported. The full expansion done by \message or other such commands, however, can't be applied carelessly to arbitrary user-supplied text. You would need to deactive problematic elements (by changing catcodes, adding \protect's, whatever). So supporting full expansion for the operand of \trimspaces is of low relevance for the envisioned normal applications. Condition 2. It suffices to remove a single space before and after the text. In almost any other programming language, a typical space-trimming function would need to handle the possibility of multiple consecutive spaces. But in text supplied by an average user through the normal TeX lexical conventions, consecutive spaces will be reduced to a single space before our trimming functions are ever called. The next installment of this `summary' will include a recently arrived solution by Jonathan Fine that handles multiple trailing spaces as easily as a single one, without any extra implementation cost. Condition 3. For both the trailing space and the leading space, we don't know whether or not they are present. If we knew for certain that a given space was present, of course, the procedure for removing it would be easier. ======================================================================== >>Solution 2 (Ian Collier) [Ian.Collier@prg.oxford.ac.uk] ... I used \meaning to find out whether or not the first character of the argument is a space (because spaces are usually ignored and this seems to be the only way to make the space visible). I'm fairly sure that "blank space" is the only \meaning beginning with "bl". I had rather a lot of trouble with braces, because if the first character is a brace then \meaning removes it and leaves an unmatched right brace. However I finally realised that \iffalse...\fi could be used to remove it. {\catcode`Q=3 \catcode`@=11 \gdef\trimspace#1{\expandafter\trimspac@a#1QAA QB} \gdef\trimspac@x#1{\trimspac@a#1QAA QB} \gdef\trimspac@a#1 Q#2{\if#2A#1\expandafter\trimspac@b \else\trimspac@c#1\fi} \gdef\trimspac@b A QB{} \gdef\trimspac@c#1QAA{#1} \gdef\trimspaces#1{\expandafter\expandafter\expandafter\tr@a \expandafter\meaning#1A\fi{#1}} \gdef\tr@a#1#2{\if#1b\if#2l\expandafter\expandafter\expandafter\tr@c \else\expandafter\expandafter\expandafter\tr@b\fi\else \expandafter\tr@b\fi} \gdef\tr@b{\expandafter\trimspace\iffalse} \gdef\tr@c{\expandafter\tr@d\iffalse} \gdef\tr@d#1{\expandafter\tr@e#1Q} \def\:{\gdef\tr@e}\: #1Q{\trimspac@x{#1}} } \def\test#1{\edef\text{#1}\immediate\write16 {"\trimspaces\text"}} \test{ Leading space} \test{Trailing space } \test{ Leading and trailing spaces } \test{Nospaces} \test{ {braces}Leading space{braces}} \test{{braces}Trailing space{braces} } \test{ {braces}Leading and trailing spaces{braces} } \test{{braces} Nospaces {braces}} \test{} \test{ } \test{\space\space{two spaces}\space\space} \end ======================================================================== Comments: Some extra work would be necessary to handle the possibility \def\text{\iftrue a\else b\fi} \trimspaces\text because removal of the \iftrue by \meaning will leave the \else and \fi unmatched, confusing the later \iffalse step done by \tr@b, \tr@c. But such a value for \text is rather unlikely in ordinary user-supplied arguments. Some more solutions to Exercise 15 will follow in a few days. Michael Downes %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% mjd@math.ams.org (Internet) ASCII 32--54,55--126: !"#$%&'()*+,-./0123456 789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ Date: 30 Dec 1993 17:07:17 -0500 (EST) From: Michael Downes Subject: Around the Bend #15, answers, 3rd installment To: info-tex@shsu.edu X-ListName: TeX-Related Network Discussion List I have done some slight condensing in the answers, indicated by "[...]". Solution 3 by Greg McGary contains an interesting idea for an alternative syntax of the \trimspaces function: Instead of writing \def\savedtitle{#1}\trimspaces\savedtitle you would write \trimmed\def\savetitle{#1} ======================================================================== >>Solution 3 (Greg McGary, gkm@tmn.com) %%% preliminaries: (Mad about those abbreviations!) \catcode`@=11 \let\ea=\expandafter \let\nx=\noexpand \let\ag=\aftergroup \def\agg{\ag\ag\ag} \let\bg=\begingroup \let\eg=\endgroup [...] %%% The underlaying tool I use is \trimmed, which is used as a modifier for %%% macro definitions to trim the trailing space from the body: %%% \trimmed\def\foo{foo } will set \foo to {foo} %%% Notice that any form of \def modifier may be interposed between \trimmed %%% and \def, as in \trimmed\global\long\outer\def\foo{foo } %%% %%% As an aside, TeX has no \expanded modifier. Expanded definitions %%% must be accomplished through use of \edef or \xdef (equivalent to %%% \global\edef) This is annoying, as we might like to use \trimmed with %%% expanded definitions and don't want to write a separate \etrimmed. %%% Luckily, we can easily roll our own \expanded modifier, like so: \def\expanded#1\def{#1\edef} %%% Other modifiers may optionally be inserted between \expanded and %%% \def, like so: \def\foo{foo} \outer\expanded\long\def\bar{\foo} %%% Here's the definition of \trimmed: \long\def\trimmed#1\def#2#3{\bg \long\def\!##1##2 \!##3\trimmed@{\eg \ifx\relax##3\relax \trimmed@{##1}##2% \else ##1{##2}% \fi}% \!{#1\def#2}#3\! \!\trimmed@} \long\def\trimmed@#1#2\!{#1{#2}} %%% Notice the use of \begingroup...\endgroup to make the definition of \! %%% temporary so as not to disturb any previous definition, and so that the %%% temporary will disappear once we're done with it. Notice that the %%% \endgroup appears right away in the body of \!, so that the ensuing \def %%% will occur in the proper group. \! was chosen as a name for the temporary %%% macro because it is a non-alphabetic (non-catcode-11) character; any other %%% non-alphabetic would suffice as well. Non-alphabetic macro-names have the %%% desirable property of preserving any trailing space token. %%% %%% If we are really fastidious about keeping clutter out of the global name %%% space, we can also define \trimmed@ as a temporary alongside \!. We would %%% also want to use a name that's already defined, to avoid entering a new %%% name into TeX's hashtable. A non-alphabetic name like \: seems like a %%% good (though cryptic) choice: \long\def\trimmed#1\def#2#3{\bg \long\def\:##1##2\!{\eg##1{##2}} \long\def\!##1##2 \!##3\:{% \ifx\relax##3\relax \:{##1}##2% \else \eg##1{##2}% \fi}% \!{#1\def#2}#3\! \!\:} %%% Notice that we've had to delay the \endgroup until after our new %%% temporary \: has been used. %%% %%% Anyway, we may now define \trimspace as follows: \def\trimspace#1{\ea\trimmed\ea\def\ea#1\ea{#1}} %%% Notice that the replacement definition is a normal \def, whereas the %%% macro we started with could have had any number of modifiers attached, %%% such as \long, \outer, or \global. A further exercise might be to fix %%% this problem. %%% %%% A more generalized trim might allow any list of tokens to be trimmed off %%% the tail of another list of tokens. Here, we add an initial argument to %%% \trimmed specifying those tokens. In order to strip off trailing ".\par" %%% for instance, we could write: \trimmed{.\par}\outer\long\def\foo{foo.\par} %%% %%% Here's the general definition of \trimmed: \long\def\trimmed#1#2\def#3#4{\bg \long\def\:##1##2\!{\eg##1{##2}} \long\def\!##1##2#1\!##3\:{% \ifx\relax##3\relax \:{##1}##2% \else \eg##1{##2}% \fi}% \!{#2\def#3}#4\!#1\!\:} %%% The auxiliary \trimmed@ remains unchanged. Notice that we no longer really %%% need a non-alphabetic macro name for the temporary macro, since we don't %%% have to preserve the literal space token following the macro. %%% %%% Unfortunately, the literal space token problem doesn't disappear, it's just %%% pushed up a level. Now we have to give that space as an argument to \trimmed %%% in the definition of \trimspace, and hop over it with \expandafter! \edef\trimspace#1{\nx\ea\nx\trimmed\nx\ea {\nx\ea\space\nx\ea}\nx\ea\def\nx\ea#1\nx\ea{#1}} %%% N.B., The curly braces, "\nx\ea{...\nx\ea}" around the "\nx\ea\space" %%% are necessary. %%% %%% This approach of defining \trimspace in terms of an underlaying \trimmed %%% \def'inition facility has the advantage of reusing code, but the %%% disadvantage of forcing a macro redefintion even if there is no trailing %%% space to remove. We could modify \trimmed to produce a new macro, \trim, %%% that redefines a macro only if it has the trailing pattern of interest. %%% (It also happens to be simpler!) \long\def\trim#1#2{\bg \long\def\!##1#1\!##2\:{\eg \ifx\relax##2\relax \else \def#2{##1}% \fi}% \ea\!#2\!#1\!\:} %%% Now, we can define \trimspace in terms of \trim like so: \edef\trimspace#1{\nx\ea\nx\trim\nx\ea{\nx\ea\space\nx\ea}\nx\ea#1} %%% Ok, let's test it: \def\HasTrailingSpace{has trailing space } \def\NoTrailingSpace{no trailing space} \trimspace\HasTrailingSpace \show\HasTrailingSpace \trimspace\NoTrailingSpace \show\NoTrailingSpace %%% While we're at it, let's test another pattern: \def\HasTrailingDotPar{has trailing dot par.\par} \def\NoTrailingDotPar{no trailing dot par} \trim{.\par}\HasTrailingDotPar \show\HasTrailingDotPar \trim{.\par}\NoTrailingDotPar \show\NoTrailingDotPar %%% ### Exercise 15(b) %%% Write a macro \trimspaces that removes a leading space, if %%% present, and then calls \trimspace to remove a trailing space. %%% I'm going to solve this in a quick and dirty way, as it's getting %%% late and I'm running out of gas! Just use \futurelet sequestered %%% in a \vbox to inspect the first token. If it's a \space, gobble %%% the first token and subject the remaining tokens to \trimmed. \def\redefSansSp@ce#1 #2\redefSansSp@ce{\def#1{#2}} \def\redefSansSpace#1{\ea\redefSansSp@ce\ea#1#1\redefSansSp@ce} \def\trimspaces#1{\bg\setbox0=\vbox{% \def\maybeRedefSansSpace{\ea\ifx\space\@\agg\redefSansSpace\agg#1\fi}% \ea\futurelet\ea\@\ea\maybeRedefSansSpace#1}\eg \trimspace#1} %%% \futurelet won't work for the more general case of trimming an %%% arbitrary leading pattern, as it only looks at one token. %%% I'll leave solving the general case as an exercise for the reader ;-) %%% %%% This is also not the most efficient solution, since we redefine the macro %%% twice if there is a leading space. Notice that we put the \setbox0 %%% inside a group, to keep any previous definition of \box0 safe. This %%% is probably overkill, since \box0 is a temporary register and users %%% should be aware that it's fair game, but it doesn't hurt to be %%% courteous... Also note the abbreviation \agg, which pushes its argument %%% out two groups. [...] %%% Testing... \def\foo{ foo } \trimspaces\foo \show\foo ======================================================================== In the previous posting I discussed the method of removing a trailing space by scanning for a token pair . In Schmitt's solution, for example, the bizarre token was a greater-than character with catcode 3. And in my solution, I used a letter Q with catcode 3. Solution 4 from Jonathan Fine takes the approach of using a second token for the token. In practice this works for typical user-supplied text, as discussed before, since TeX's normal reduction of multiple spaces to single spaces makes the pair sufficiently bizarre. I have to admit I like this idea; those who attempted a solution for this exercise and struggled with various other delimiter possibilities will, I think, appreciate the humor of it as I did. As I mentioned last week, I found some theoretical interest in the fact that if multiple space tokens were present at the end of the text being trimmed, Fine's solution would remove them all, without needing to use recursion. But another correspondent pointed out since then that if multiple spaces were present at the end they might also be presumed possible in the middle of the scanned text, and an occurrence of multiple spaces in the middle would cause \trim to fail. ======================================================================== >>Solution 4 (Jonathan Fine) %% NOTE: I have benefited from Michael Downes posting of answers, dated %% 16 December, particularly for stripping the leading space, and the %% discussion of the hazards of grouped arguments \catcode`\@=11 %% The Solution \def\trim #1{\expandafter\trim@\expandafter{#1 }#1} \def\trim@ #1{\trim@@ @#1 @ #1 @ @@} \def\trim@@ #1@ #2@ #3@@{\trim@@@\empty #2 @} \def\unbrace#1{#1} \unbrace{\def\trim@@@ #1 } #2@#3{\expandafter\def \expandafter #3\expandafter {#1}} %% Test Code \def\Test{\afterassignment\Test@ \def\test} \def\Test@{\trim\test \afterassignment\Test@@ \def\test@} \def\Test@@{\message{\ifx\test\test@ Y\else FAIL:|\meaning\test|\fi}} \catcode`\@=12 %% Testing The Solution \Test{}{} \Test{ }{} \Test{ a }{a} \Test{ {}{} }{{}{}} \Test{{braces}}{{braces}} \Test{ {braces} }{{braces}} \Test{ { braces } }{{ braces }} \Test{no space and no space}{no space and no space} \Test{no space and a space: }{no space and a space:} \Test{ :a space and no space}{:a space and no space} \Test{ :a space and a space: }{:a space and a space:} \Test{ \ifx }{\ifx} \Test{ \ifx/ }{\ifx/} ======================================================================== Since my solution got rather long after I added some commentary I'll post it separately in a couple of days, rather than double the size of this post. Michael Downes %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% mjd@math.ams.org (Internet) ASCII 32--54,55--126: !"#$%&'()*+,-./0123456 789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ Date: 03 Jan 1994 17:14:14 -0500 (EST) From: Michael Downes Subject: Around the Bend #15, answers, 4th (last) installment To: info-tex@shsu.edu X-ListName: TeX-Related Network Discussion List My solution here is the result of weeks of incremental refinement, ending only last week, and consequently benefits from analysis of the other solutions. ======================================================================== >>Solution 5 (Michael Downes) % Here I only solve part (b) of Exercise 15, in an attempt to make % a solution of utmost compactness (3 control sequences, 45 tokens). % Also, it seems likely that in actual use \trimspaces can be % applied without harm whenever \trimspace might be needed. % % The method for pausing after each test might be of ancillary % interest to some readers; unlike the alternative of setting % \pausing=1, the \test's aren't required to be on separate lines. \catcode`\Q=3 % \trimspaces\x redefines \x to have the same replacement text sans % leading and trailing space tokens. % \def\trimspaces#1{% % Use grouping to emulate a multi-token afterassignment queue. \begingroup % Put `\toks 0 {' into the afterassignment queue. \aftergroup\toks\aftergroup0\aftergroup{% % Apply \trimb to the replacement text of #1, adding a leading % \noexpand to prevent brace stripping and to serve another purpose % later. \expandafter\trimb\expandafter\noexpand#1Q Q}% % Transfer the trimmed text back into #1. \edef#1{\the\toks0}% } % \trimb removes a trailing space if present, then calls \trimc to % clean up any leftover bizarre Qs, and trim a leading space. In % order for \trimc to work properly we need to put back a Q first. % \def\trimb#1 Q{\trimc#1Q} % Execute \vfuzz assignment to remove leading space; the \noexpand % will now prevent unwanted expansion of a macro or other expandable % token at the beginning of the trimmed text. The \endgroup will feed % in the \aftergroup tokens after the \vfuzz assignment is completed. % \def\trimc#1Q#2{\afterassignment\endgroup \vfuzz\the\vfuzz#1} \catcode`\Q=11 \def\test#1{\errhelp{#1}\message{[\the\errhelp]}% \edef\x{\the\errhelp}% \global\tracingcommands2\global\tracingmacros2\global\tracingonline0 \trimspaces\x \global\tracingcommands0\global\tracingmacros0\global\tracingonline0 \errhelp\expandafter{\x}\message{-> [\the\errhelp]}% \read16 to\PressReturnToContinue } \test{ x } \test{ xy z } \test{} \test{{}} \test{{}{}} \test{ {x} } \test{ } \test{{ }} \test{\AA} \test{\fi} \test{\space x\space} \test{ #1 } \end ------------------------------------------------------------------------ Commentary Suppose we have a macro \x with replacement text " {xyz} ". The task of \trimspaces is to construct a statement of the form \def\x{{xyz}} i.e., to redefine \x with the same replacement text except for removal of a leading or trailing space. However, a similar statement \toks0{{xyz}}\edef\x{\the\toks0} is more robust if the replacement text might contain # tokens. For example, \def\x{\def\y##1{}} works OK but after thus defining \x, the statements \def\trimx#1{\expandafter\def\expandafter\x\expandafter{#1}} \trimx\x fail with an error message because the "#1" in the definition of \y is misinterpreted as a parameter token for the redefinition of \x. Although # tokens seem highly unlikely in average user-supplied text, I aimed for a statement of the second, robuster kind, as if I were writing \trimspaces for use in a major macro package with thousands of prospective users. The basic structure of \trimspaces is therefore: First remove a trailing space, then remove a leading space, then put the remaining text into \toks0, then transfer the text to \x with \edef. For removing the trailing space, I apply a macro scan with delimiter . Here the notation means the character token consisting of character code c with catcode n. The leading space is removed by executing the assignment \vfuzz=\the\vfuzz at the beginning of the operand text, in order to use a side effect of the assignment: removal of a following space. (Credit to Donald Arseneau for this good idea.) The main reason for using "\the\vfuzz" instead of 0pt is that it's slightly shorter (one token), although if we did not have the group structure to localize the `change' to \vfuzz, then using "\the\vfuzz" would also be a good idea for the sake of preserving the variable's previous value. The statement \vfuzz=\vfuzz (sans \the), by the way, would not gobble a following space: when TeX recognizes a suitable variable on the right-hand side of an assignment, it copies the value directly into the left-hand side and skips the scanning process entirely. Here's a step-by-step breakdown of the operation of \trimspaces through two possibilities, one where both a leading and a trailing space are present, and one where neither are present. ------------------------------------------------------------------------ Case 1 (spaces present) Case 2 (no spaces to be removed) ------------------------------------------------------------------------ \def\x{ {xyz} } \trimspaces\x \def\x{{xyz}} \trimspaces\x Step 1: Step 1: \begingroup... Same as for Case 1. \expandafter\trimb \expandafter\noexpand\x Q Q}... Step 2: || Step 2: || \trimb\noexpand {xyz} Q Q... \trimb\noexpand{xyz}Q Q... ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ Here the row of ^^^ indicates the In this case the first Q is taken material that is taken as argument up as part of #1, which is passed #1 of \trimb, and || indicates the to \trimc. The second Q added by tokens that match the macro \trimb therefore falls after the delimiter. #1 is now passed to leftover Q instead of before. \trimc, with another Q token added; the leftover Q token pair follows. Step 3: | Step 3: | \trimc\noexpand {xyz}Q Q... \trimc\noexpand{xyz}QQ... ^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^ ^ Here we have #1, delimiter token Q, The situation at the end of the and #2. The space before the second trimmed text ends up being the same Q is skipped by TeX because it's as in Case 1, except for the looking for a nondelimited argument absence of a space between the Qs. for #2. Step 4: Step 4: \afterassignment\endgroup \afterassignment\endgroup \vfuzz\the\vfuzz\noexpand {xyz}}... \vfuzz\the\vfuzz\noexpand{xyz}}... ^ Here the ^ marks the leading space that is to be removed. Step 5: \endgroup{xyz}}... Step 5: \endgroup{xyz}}... \endgroup is from \afterassignment. Step 6: Step 6: \toks0{{xyz}} \toks0{{xyz}} ^^^^^^^---from \aftergroup ^^^^^^^---from \aftergroup \edef\x{\the\toks0} \edef\x{\the\toks0} ======================================================================== That's a wrap on Exercise 15. Michael Downes %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% mjd@math.ams.org (Internet) ASCII 32--54,55--126: !"#$%&'()*+,-./0123456 789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~