5

The text book that I'm writing requires the ability to dynamically build an alphabetically sorted list. As to not invoke the overhead of high powered, general purpose packages, I have opted instead to write my own, which is both fun and instructive.

Any alphabetic sorting routine must use a sort key to determine the sorted position in the list of the current phrase by comparing its sort key with key of each of other phrases in the list. That routine implements the desired sorting rules such as ignore case, numbers, spaces and punctuation marks for example, as well as strip out any embedded formatting commands which include font and font family changes and accent (diacritical) marks (zap the accent, keep the character). This routine will not only drop characters (spaces and punctuation, etc.), but could directly alter the character itself, such as changing its case for example. One way to accomplish all these tasks is to march through the phrase being sorted character by character, applying these rules to each character individually, and finally build the key by appending the resulting character to the string of already processed characters. It is this last step, appending a character to a string, that is the basis of this question.

I have implemented this appending process using a (global) \edef statement as shown in the \appendChar macro in the following MWE:

\documentclass{article}

\usepackage[svgnames]{xcolor}% to get named colors

\def\RLp{{\color{Red}\tt(}} \def\RRp{{\color{Red}\tt)}}% (input)
\def\RLb{{\color{Red}\tt[}} \def\RRb{{\color{Red}\tt]}}% [output]

\gdef\ttt{\tt\char92}% A better \tt \textbackslash

\def\appendChar#1{% Append argument character to string
  \global\edef\resultStr{\resultStr#1}%
  \par{\tt~\RLp#1\RRp~~~\RLb\resultStr\RRb}\par%
}

\def\testAppendOne#1{% Use counter and \chardef; FAILS
  \newcount\chNum \chNum=\numexpr(`#1)%
% Do arithmetic on the counter to implement the sorting rules
  \chardef\myCh=\chNum%
  \appendChar{\char\myCh}%
}

\def\testAppendTwo#1{% Use counter, \chardef and \the ; WORKS
  \newcount\chNum \chNum=\numexpr(`#1)%
% Do arithmetic on the counter to implement the sorting rules
  \chardef\myCh=\chNum%
  \appendChar{\char\the\myCh}%
}

\def\testAppendThree#1{% Bypass \chardef, WORKS
  \newcount\chNum \chNum=\numexpr(`#1)%
% Do arithmetic on the counter to implement the sorting rules
  \appendChar{\char\the\chNum}%
}

\def\useMacro#1{% Calls macro #1{<character>}
  \global\edef\resultStr{}% (Re)initialize to empty string:
  \medskip
  \par{\tt\RLp chr\RRp~~\RLb\ttt resultStr\RRb}\par
  #1{a}% Simulate character data from string parsing macro
  #1{b}%
  #1{c}%
  #1{d}%
  #1{e}%
  \medskip
}

\begin{document} %               B E G I N   D O C U M E N T

% Proof of principle of using \edef to rebuild a string
\noindent{\Large\bf\color{Blue}Using {\ttt resultStrAppend}:}
\useMacro{\appendChar}

% First attempt fails with apparent recursion
\bigskip\noindent{\Large\bf\color{Blue}Using {\ttt testAppendOne}:}
\useMacro{\testAppendOne}

% Second attempt: adding \the command eliminates recursion
\bigskip\noindent{\Large\bf\color{Blue}Using {\ttt testAppendTwo}:}
\useMacro{\testAppendTwo}

% Third (final) macro: don't need the \chardef, have working macro
\bigskip\noindent{\Large\bf\color{Blue}Using {\ttt testAppendThree}:}
\useMacro{\testAppendThree}

\end{document} %                 E N D   D O C U M E N T

We call the \appendChar macro four different ways:

  1. Using \appendChar: First we call \appendChar directly to provide a "proof of principle" of using an \edef statement to (re)construct a string with what at first appears to be recursion, but clearly is not as seen in the screenshot.
  2. Using \testAppendOne: This processes the input character using the \chNum counter and a \chardef statement prior to passing it to \appendChar. The resulting string, \resultStr, is completely wrong. After the nth character, all n characters in \resultStr have been changed into the character just input! This certainly looks like recursion, but is it, really?
  3. Using \testAppendTwo: This macro is identical to \testAppendOne except for the addition of the \the command to the call to \appendChar. We find that this stops the "recursion" and generates the correct result.
  4. Using \testAppendThree: After some experimentation, it was found that the \chardef was not needed. This is the final, working macro.

Item 2 above is the heart of this question. Even though I have a working solution to the append process, the odd behavior of the \testAppendTwo macro caught my attention and I sought to understand what was happening. I have been unsuccessful. Hence, my question is:

What, exactly, is happening in the \testAppendOne macro? I'm not only interested in a description of what's happening, but also please describe what steps I should take to unravel this behavior for myself.

I suspect that would include using some of the \tracing... macros, but I have not found a usable description of how to use these macros, vs. what they do, and even that is very sketchy.

Test output

  • I tried to find a tag for \tracing... but could not. As I cannot yet create tags, would it not be useful to have such a tag? If so, could someone with that privilege create it? – OneMug May 17 at 18:13
  • Do you mean “the odd behavior of the \testAppendOne”? – Phelype Oleinik May 17 at 18:24
  • unrelated to the question but (apart from the that \tt shouldn't be used in latex), bware definitions such as \gdef\ttt{\tt\char92}% try \ttt 123 for example. – David Carlisle May 17 at 18:42
  • @PhelypeOleinik Yes, the odd behavior of the \testAppendOne macro – OneMug May 17 at 18:42
  • 1
    @OneMug You can use \input color to get the color package in plain TeX. I don't think xcolor can be used, though. – Phelype Oleinik May 17 at 18:57
6

First some general comments about your code:

In LaTeX use \bfseries instead of \bf and \ttfamily instead of \tt. The 2-letter font changing commands are obsolete since LaTeX2ε was released more than 20 years ago.

\global\edef can be shortened to \xdef. However, a macro does not create a group level, so since you are using this all without any grouping layer, the \global assignments are unnecessary here (unless, of course, in your actual code you need it, then ignore this).

One trick for printing command names is that what you achieve with \ttt command you can do with \ttfamily\string\command or, if you define \def\ttt{\ttfamily\string}, then \ttt\command.

One quite important thing is: do not allocate a register more than once. You are just depleting the number of available registers. Allocate the registers once an use them as many times as you need. I moved \newcount\chNum outside the macro definition.

In number assignments TeX already understands \chNum=`#1 to get the numerical value of an alphabetic constant; you don't need (but it doesn't hurt either) \numexpr here. Also the extra pair of parentheses around `#1 in the \numexpr aren't needed here.

Now on to \testAppendOne. The code

\chNum=\numexpr(`#1)%
\chardef\myCh=\chNum%
\appendChar{\char\myCh}%

appends the two tokens \char\myCh to \resultStr. However, since the \char primitive is not expandable the contents of \resultStr are \char\myCh after the first iteration, \char\myCh\char\myCh after the second one, and so on. When you ask TeX to write the contents of \resultStr it will use \char\myCh\char\myCh... and \myCh will have the last value assigned to it, which will be the last letter you processed, thus the behaviour you see.

When you process a \myCh is \char"61, so \char\char"61 will be a. Pretty obvious. When you process b \myCh is \char"62 then \char\char"62\char\char"62, which is bb and so on. To fix this you have to give \appendChar something that will not change if the \myCh change. The code

\chNum=\numexpr(`#1)%
\chardef\myCh=\chNum%
\appendChar{\char\myCh}%

is redundant. You don't need to \chardef a character before passing it to \char. As you have yourself done for the backslash with \char92, you can just use the number here:

\chNum=`#1
\appendChar{\char\chNum}%

or even:

\appendChar{\char`#1}%

This will pass \char`<some number which will not change> to \appendChar and you'll get what you want.

But why did \char\the\myCh in \testAppendTwo work? Because the primitive \the will access the value in \myCh. Suppose \myCh is \char"61 (a), then \the\myCh will be "61 (hexadecimal) or 97. Thus the code

\chNum=\numexpr(`#1)%
\chardef\myCh=\chNum
\showthe\myCh
\appendChar{\char\the\myCh}%

is also redundant because \the\myCH will access the number in \myCh, which is \chNum, so you can simplify to:

\chNum=\numexpr(`#1)%
\appendChar{\char\chNum}%

\testAppendThree is the same, except that \the\chNum will get the value in the \count register instead of the \char. But they are the same, thus the result is the same (and is the same as the modified version of \testAppendTwo :).


Now to the second part how to debug TeX code... Well... You don't really want to enter here. You'll be happier if you don't (just kidding... or am I?).

You code is rather straightforward, so I managed to debug it using only \show and \showthe. They both are used as \show<macro> and \showthe<register>.

\show prints the contents of a macro to the console. For example, if I change your \appendChar macro to:

\def\appendChar#1{% Append argument character to string
  \xdef\resultStr{\resultStr#1}%
  \show\resultStr
  \par{\ttfamily~\RLp#1\RRp~~~\RLb\resultStr\RRb}\par%
}

and run the \testAppendOne TeX will show this in the terminal:

> \resultStr=macro:
->\char \myCh .
\appendChar #1->\xdef \resultStr {\resultStr #1}\show \resultStr 
                                                                 \par {\ttfamily ~\RLp #1\RRp ~~~\RLb \resultStr \RRb }\par 
l.63 \useMacro{\testAppendOne}

? 

then, if I press <return>, it shows:

> \resultStr=macro:
->\char \myCh \char \myCh .
\appendChar #1->\xdef \resultStr {\resultStr #1}\show \resultStr 
                                                                 \par {\ttfamily ~\RLp #1\RRp ~~~\RLb \resultStr \RRb }\par 
l.63 \useMacro{\testAppendOne}

? 

(notice the second line). This showed me why your \testAppendOne was printing the same letter multiple times as I described earlier.

\showthe is similar, but it shows the value of a register. For instance, if you want to see the value of the \chNum register you'd use \showthe\chNum.

These two macros have equivalents which, instead of printing to the console (and the log) print to the PDF. \meaning<macro> will print the definition of the <macro> and \the<register> will print its value.


Not enough debugging?

You can use the \tracingall macro, which tells TeX “give me all you have to show” (and then \tracingnone when you want it to stop), and then your console will be flooded with text. With some practice you'll learn how to read that.

You can also load the trace package and use \traceon/\traceoff, which will hide some steps of the code which will print rather long trace text, like LaTeX's font selection system.


Still not enough?

Load \usepackage{unravel} (after all you asked for unravel :) and use \unravel{<code which is keeping you awake at night>}, and the package will show you step-by-step what TeX is doing. It's pretty useful when you are stuck at some nasty piece of code. Beware that this step-by-step is really thorough, so it might take a handful steps to \unravel{\useMacro{\appendChar}} :)

  • Your comments about \bf and \tt really dates me, doesn't it! I started using LaTeX over 30 years ago. Old habits die hard! Regarding \xdef instead of \global\edef, I do use \xdef in my code, but changed it for this question to be consistent with the \edef I used in the title of the question. (This comment got too long, so I had to break it into smaller chunks.) – OneMug May 17 at 19:45
  • continuing: About having the \newcount*inside* the macro, this is a scoping issue I've been trying to solve for some time. Before your answer here I was not certain that a \def did not create a group so that internal definitions/allocations would be gone when the macro ended. A very subtle, but crucial point that has NOT been made clear to me in the last 30 years! Thank you for that input! ... – OneMug May 17 at 19:48
  • @OneMug You're welcome :-) Sure, there is no problem with \global\edef; I just though you didn't know of \xdef. About \newcount: register allocation is always global, so {\newcount\test}\show\test\bye will show \test=\count27, meaning that the register is not restored after the group ends, so there is really no reason to do it more than once. In fact, if you try \def\tset{\newcount\test} in plain you'll get an error because \newcount is an \outer macro to avoid this type of automatic allocation which, at the time, would shortly leave you without any more space for registers. – Phelype Oleinik May 17 at 20:02
  • I have noticed that when I move a piece of code from 'LaTeX` to plain-TeX (I use pdfLaTeX and pdfteX engines respectively), I have to move any \new...'s (\newcount, \newif, ...) defined inside a \def that those statements must be moved outside of the \def before it will compile. Is this a symptom of you are describing? – OneMug May 17 at 20:57
  • 1
    Thanks for the explanation and for all the good debugging advice. It's already paying dividends! – OneMug May 19 at 2:11
5

You are using

  \chardef\myCh=\chNum%
  \appendChar{\char\myCh}%

neither \char nor a \chardef defined token such as \myCh is expandable so your repeated \edef just produces

\char\myCh\char\myCh\char\myCh\char\myCh

and when you finally use the macro you just get multiple copies of the last defined \myCh

  • Thank you for this explanation. I did not know that \myCh would not be expandable. When I added the statement \message{Am in appendChar, arg = #1} just after \def\appendChar#1{%, when called from \testAppendOne the output was Am in appendChar, arg = \char \myCh. Is the presence there of \char \myCh rather that the letters a, b, c, etc. a signal of that lack of expandability? – OneMug May 17 at 21:15
  • 1
    @OneMug yes \message and \edef expand their arguments in similar ways. note \char\myCh is more or less the same thing as \myCh the \char is not doing anything useful, it isn't clear from this MWE why you don't simply add the original letter rather than going through a number, but perhaps that is needed in your real document. – David Carlisle May 17 at 21:20
  • Thanks again! This is one of those little debugging gems that I will keep handy. I meant to also mention in my previous comment that repeating the same line as the \message{...}, but without the \message would, when compiled, print the actual character (the a, b, ...) rather than the \char \myCh that went to the console. I take it that the different outputs could be viewed as a flag that some kind of expansion issue is going on, correct? – OneMug May 17 at 21:51
  • As I tried to make it clear in the text of my question; I run the characters thru a counter because I may want to change the character itself. For a concrete example, I use this technique to perform my own \mkLowerCase function by modifying the counter that holds the character's original character code with a statement like \ifnum\chNum<\numexpr('a)\advance\chNum\numexpr('a-'A)\fi (after ensuring that this character's catcode is 11 of course) then using the modified counter to 'build' a lower cased character to replace the original character. – OneMug May 17 at 22:14
  • @OneMug Even though I agree with your statement about the joy of writing macros/reinventing the wheel (I do that all the time :-), upper/lower casing text is tricky. I'd suggest you use or take a look at LaTeX's \MakeUppercase/\MakeLowercase or expl3's \tl_upper_case:n/\tl_lower_case:n. The latter has unicode support when compiled with XeTeX and LuaTeX, is fully expandable (\message{\tl_upper_case:n{hello}} will do what you expect it to), and is format independent. – Phelype Oleinik May 18 at 1:10
5

You can debug by adding something like

\texttt{\meaning\resultStr}

to the output. If you do it on your original code you'd get, for \testAppendOne,

enter image description here

For \testAppendTwo and \testAppendThree you'd get

enter image description here

which is not what you want either, is it?

Here's a fixed version of \testAppendOne that appends the real character with a \lowercase trick, namely

\def\testAppendOne#1{% Use a better approach; WORKS
  \chNum=\numexpr(`#1)\relax
% Do arithmetic on the counter to implement the sorting rules
  \begingroup\lccode`!=\chNum\lowercase{\endgroup\appendChar{!}}%
}

Note other fixes: \ttfamily is used instead of \tt and similarly \bfseries for \bf; more important, you should not use \newcount\chNum at each call: you're wasting a count register at each macro call. I also streamlined the initial macros.

Why do I use \lccode`!=\chNum? The character ! has category code 12, so we essentially normalize all category codes to 12, which seems appropriate when dealing with strings.

\documentclass{article}

\usepackage[svgnames]{xcolor}% to get named colors

\newcommand{\Rtt}[1]{\textcolor{Red}{\ttfamily#1}}
\newcommand\RLp{\Rtt{(}}\newcommand\RRp{\Rtt{)}} % input
\newcommand\RLb{\Rtt{[}}\newcommand\RRb{\Rtt{]}} % [output]

\newcommand\ttt{{\ttfamily\char92}} % A better \tt \textbackslash

\newcount\chNum % NOT inside macros

\def\appendChar#1{% Append argument character to string
  \global\edef\resultStr{\resultStr#1}%
  \par{\ttfamily~\RLp#1\RRp~~~\RLb\resultStr\RRb}\par
}

\def\testAppendOne#1{% Use a better approach; WORKS
  \chNum=\numexpr(`#1)\relax
% Do arithmetic on the counter to implement the sorting rules
  \begingroup\lccode`!=\chNum\lowercase{\endgroup\appendChar{!}}%
}

\def\testAppendTwo#1{% Use counter, \chardef and \the; DOESN'T REALLY WORK
  \chNum=\numexpr(`#1)\relax
% Do arithmetic on the counter to implement the sorting rules
  \chardef\myCh=\chNum
  \appendChar{\char\the\myCh}%
}

\def\testAppendThree#1{% Bypass \chardef, DOESN'T REALLY WORK
  \chNum=\numexpr(`#1)\relax
% Do arithmetic on the counter to implement the sorting rules
  \appendChar{\char\the\chNum}%
}

\def\useMacro#1{% Calls macro #1{<character>}
  \global\edef\resultStr{}% (Re)initialize to empty string:
  \medskip
  \par{\tt\RLp chr\RRp~~\RLb\ttt resultStr\RRb}\par
  #1{a}% Simulate character data from string parsing macro
  #1{b}%
  #1{c}%
  #1{d}%
  #1{e}%
  \medskip
  \texttt{\meaning\resultStr}% for debugging
  \medskip
}

\begin{document} %               B E G I N   D O C U M E N T

% Proof of principle of using \edef to rebuild a string
\noindent{\Large\bfseries\color{Blue}Using {\ttt resultStrAppend}:}
\useMacro{\appendChar}

% First attempt fails with apparent recursion
\bigskip\noindent{\Large\bfseries\color{Blue}Using {\ttt testAppendOne}:}
\useMacro{\testAppendOne}

% Second attempt: adding \the command eliminates recursion
\bigskip\noindent{\Large\bfseries\color{Blue}Using {\ttt testAppendTwo}:}
\useMacro{\testAppendTwo}

% Third (final) macro: don't need the \chardef, have working macro
\bigskip\noindent{\Large\bfseries\color{Blue}Using {\ttt testAppendThree}:}
\useMacro{\testAppendThree}

\end{document}

enter image description here

  • RE: lowercase trick - I get how \lccode works here and why it's used inside a \begingroup / \endgroup, but the placement of the \endgroup puzzles me! How does \lowercase apply the \lccode changes made inside the group to its argument when that argument is outside the group. i.e., how did those changes propagate past the \endgroup? – OneMug May 18 at 16:35
  • @OneMug First TeX does the lowercasing (only ! is affected), then processes the resulting token list. So \endgroup is performed when the lowercasing has already been done. – egreg May 18 at 16:37
  • When you say that "only ! is affected", what, exactly, has been "affected"? The character code for ! in the lccode table was changed to that of the argument character, #1, but the catcode in that table for ! was not changed to the argument's catcode (11) but kept as the !'s catcode (12) when \lccode was processed. Is this what you mean by 'affected', or do you mean when that table is referenced when the ! character in the argument to \appendChar is processed by \lowercase? – OneMug May 18 at 16:52
  • 1
    @OneMug When \lowercase{\endgroup\appendChar{!}} is processed, control sequences are not changed, only characters are modified according to the \lccode table. So, if the \lccode of ! has been set to 97, the token list \endgroup\appendChar{a} will be reinserted in the main token list; however, the category code doesn't change when \lowercase or \uppercase do their work, so a will have category code 12. – egreg May 18 at 17:04
  • Got it! Thank you. One more question though: You stated that "we essentially normalize all category codes to 12, which seems appropriate when dealing with strings." Why do you feel cat 12 seems appropriate when dealing with strings? I would think cat 11 would be more appropriate in light of the fact that my 'sort rules' specifically state that all punctuation is being dropped, and punctuation characters have cat code 12. – OneMug May 18 at 17:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.