From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- info/challenges/aro-bend/answer.015 | 830 ++++++++++++++++++++++++++++++++++++ 1 file changed, 830 insertions(+) create mode 100644 info/challenges/aro-bend/answer.015 (limited to 'info/challenges/aro-bend/answer.015') diff --git a/info/challenges/aro-bend/answer.015 b/info/challenges/aro-bend/answer.015 new file mode 100644 index 0000000000..811e34eedb --- /dev/null +++ b/info/challenges/aro-bend/answer.015 @@ -0,0 +1,830 @@ +[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{|}~ -- cgit v1.2.3