The trace package^{∗}

Frank Mittelbach L^{A}TEX3 project

            frank.mittelbach@latex-project.org

2001/07/24

1 Introduction

When writing new macros one often finds that they do not work as expected (at least I do :-). If this happens and one can’t immediately figure out why there is a problem one has to start doing some serious debugging. TEX offers a lot of bells and whistles to control what is being traced but often enough I find myself applying the crude command \tracingall which essentially means “give me whatever tracing information is available”.

In fact I normally use ε-TEX in such a case, since that TEX extension offers me a number of additional tracing possibilities which I find extremely helpful. The most important ones are \tracingassigns, which will show you changes to register values and changes to control sequences when they happen, and \tracinggroups, which will tell you what groups are entered or left (very useful if your grouping got out of sync).

So what I really write is

  \tracingassigns=1\tracinggroups=1\tracingall

That in itself is already a nuisance (since it is a mouthful) but there is a worse catch: when using \tracingall you do get a awful lot of information and some of it is really useless.

For example, if L^{A}TEX has to load a new font it enters some internal routines of NFSS which scan font definition tables etc. And 99.9% of the time you are not at all interested in that part of the processing but in the two lines before and the five lines after. However, you have to scan through a few hundred lines of output to find the lines you need.

Another example is the calc package. A simple statement like \setlength \linewidth {1cm} inside your macro will result in

  \setlength ->\protect \setlength
  {\relax}

∗This file has version number v1.1b, last revised 2001/07/24.

\setlength  ->\calc@assign@skip
\calc@assign@skip ->\calc@assign@generic \calc@Askip \calc@Bskip
\calc@assign@generic #1#2#3#4->\let \calc@A #1\let \calc@B #2\expandafter \calc
@open \expandafter (#4!\global \calc@A \calc@B \endgroup #3\calc@B
#1<-\calc@Askip
#2<-\calc@Bskip
#3<-\linewidth
#4<-1cm
{\let}
{\let}
{\expandafter}
{\expandafter}
\calc@open (->\begingroup \aftergroup \calc@initB \begingroup \aftergroup \calc
@initB \calc@pre@scan
{\begingroup}
{\aftergroup}
{\begingroup}
{\aftergroup}
\calc@pre@scan #1->\ifx (#1\expandafter \calc@open \else \ifx \widthof #1\expan
dafter \expandafter \expandafter \calc@textsize \else \calc@numeric \fi \fi #1
#1<-1
{\ifx}
{false}
{\ifx}
{false}
\calc@numeric ->\afterassignment \calc@post@scan \global \calc@A
{\afterassignment}
{\global}
{\fi}
{\fi}
\calc@post@scan #1->\ifx #1!\let \calc@next \endgroup \else \ifx #1+\let \calc@
next \calc@add \else \ifx #1-\let \calc@next \calc@subtract \else \ifx #1*\let
\calc@next \calc@multiplyx \else \ifx #1/\let \calc@next \calc@dividex \else \i
fx #1)\let \calc@next \calc@close \else \calc@error #1\fi \fi \fi \fi \fi \fi \
calc@next
#1<-!
{\ifx}
{true}
{\let}
{\else}
{\endgroup}
{restoring \calc@next=undefined}
\calc@initB ->\calc@B \calc@A
{\skip44}
{\global}
{\endgroup}
{restoring \skip44=0.0pt}
\calc@initB ->\calc@B \calc@A

  {\skip44}
  {\dimen27}

Do you still remember what I was talking about?

No? We’re trying to find a problem in macro code without having to scan too many uninteresting lines. To make this possible we have to redefine a number of key commands to turn tracing off temporarily in the hope that this will reduce the amount of noise during the trace. For example, if we change one of the calc internals slightly, the above tracing output can be reduced to:

  \setlength ->\protect \setlength
  {\relax}
  \setlength  ->\calc@assign@skip
  \calc@assign@skip ->\calc@assign@generic \calc@Askip \calc@Bskip
  \calc@assign@generic #1#2#3#4->\let \calc@A #1\let \calc@B #2\expandafter \calc
  @open \expandafter (#4!\global \calc@A \calc@B \endgroup #3\calc@B
  #1<-\calc@Askip
  #2<-\calc@Bskip
  #3<-\linewidth
  #4<-1cm
  {\let}
  {\let}
  {\expandafter}
  {\expandafter}
  \calc@open (->\begingroup \conditionally@traceoff \aftergroup \calc@initB \begi
  ngroup \aftergroup \calc@initB \calc@pre@scan
  {\begingroup}
  \conditionally@traceoff ->\tracingrestores \z@ \tracingcommands \z@ \tracingpag
  es \z@ \tracingmacros \z@ \tracingparagraphs \z@
  {\tracingrestores}
  {\tracingcommands}
  {restoring \tracingrestores=1}
  \calc@initB ->\calc@B \calc@A
  {\skip44}
  {\dimen27}

Still a lot of noise but definitely preferable to the original case.

I redefined those internals that I found most annoyingly noisy. There are probably many others that could be treated in a similar fashion, so if you think you found one worth adding please drop me a short note.

∗ ∗ ∗

\traceon The package defines the two macros \traceon and \traceoff to uncondi- \traceoff tionally turn tracing on or off, respectively. \traceon is like \tracingall but

additionally adds \tracingassigns and \tracinggroups if the ε-TEX program (in extended mode) is used. And \traceoff will turn tracing off again, a command which is already badly missing in plain TEX, since it is often not desirable to restrict the tracing using extra groups in the document.

\conditionally@traceon There are also two internal macros that turn tracing on and off, but only if \conditionally@traceoff the user requested tracing in the first place. These are the ones that are used

internally within the code below.

Since the package overwrites some internals of other packages you should load it as the last package in your preamble using \usepackage{trace}.

The package offers one option (logonly) that suppresses terminal output during tracing. This is useful if the TEX implementation used gets rather slow when writing a lot of information to the terminal.

2 A sample file

The following small test file shows the benefits of the trace package. If one uncomments the line loading the package, the amount of tracing data will be drastically reduced. Without the trace package we get 6594 lines in the log file; adding the package will reduce this to 1618 lines.

  \documentclass{article}
  \usepackage{calc}
  %\usepackage{trace} % uncomment to see difference
  \begin{document}
  \ifx\traceon\undefined \tracingall \else \traceon \fi
  \setlength\linewidth{1cm}
  $foo=\bar a$
  \small \texttt{\$}  \stop

3 Implementation

This package is for use with L^{A}TEX (though something similar could be produced for other formats).

1 ⟨∗package⟩

 2 \NeedsTeXFormat{LaTeX2e}[1998/12/01]

The package has one option that suppresses tracing on the terminal, i.e., if used will not set \tracingonline to one. This has been added in version 1.1a since some TEX implementations get rather slow when outputting to a terminal.

 3 \DeclareOption{logonly}
 4 {\let\tracingonline@p\z@}

The default is showing the tracing information on the terminal.

 5 \let\tracingonline@p\@ne
 6 \ProcessOptions\relax

\if@tracing We need a switch to determine if we want any tracing at all. Otherwise, if we use \traceoff… \traceon internally, we would unconditionally turn on tracing even when no tracing was asked for in the first place.

 7 \newif\if@tracing

\traceon \conditionally@traceoff As stated in the introduction, the amount of tracing being done should depend on the formatter we use. So we first test if we are running with ε-TEX in extended mode. In the latter case the command \tracinggroups is defined.^{1}

 8 \ifx\tracinggroups\undefined

If we are using standard TEX then \traceon is more or less another name for \tracingall. The only differences are that we set the above @tracing switch to true and reorder the assignments within it somewhat so that it will output no tracing information about itself. In contrast, \tracingall itself produces

  {vertical mode: \tracingstats}
  {\tracingpages}
  {\tracinglostchars}
  {\tracingmacros}
  {\tracingparagraphs}
  {\tracingrestores}
  {\errorcontextlines}
  \showoutput ->\tracingoutput \@ne \showboxbreadth \maxdimen \showboxdepth \maxd
  imen \errorstopmode \showoverfull
  {\tracingoutput}
  {\showboxbreadth}
  {\showboxdepth}
  {\errorstopmode}
  \showoverfull ->\tracingonline \@ne
  {\tracingonline}

Which is quite a lot given that none of it is of any help to the task at hand. In contrast \traceon will produce nothing whatsoever since the noise generating switches are set at the very end.

 9 \def\traceon{%

We start by setting the @tracing switch to signal that tracing is asked for. This is then followed by setting the various tracing primitives of TEX.

10  \@tracingtrue
11  \tracingstats\tw@
12  \tracingpages\@ne
13  \tracinglostchars\@ne
14  \tracingparagraphs\@ne
15  \errorcontextlines\maxdimen
16  \tracingoutput\@ne
17  \showboxbreadth\maxdimen
18  \showboxdepth\maxdimen

1If some package writer has defined that command name for some reason—too bad—then we make the wrong deduction from this fact and as a result the package will fail.

19  \errorstopmode
20  \tracingmacros\tw@
21  \tracingrestores\@ne
22  \tracingcommands\tw@

The setting of \tracingonline depends on the option logonly:

23  \tracingonline\tracingonline@p

24 }

Now what should \conditionally@traceoff do in this case? Should it revert all settings changed by \traceon? It should not, since our goal is to shorten the trace output, thus setting all of the uninteresting values back makes the output unnecessarily longer. Therefore we restrict ourself to those \tracing... internals that really contribute to listings like the above.

And one additional point is worth mentioning. The order in which we turn the tracing internals off has effects on the output we see. So what needs to be

turned off first? Either \tracingrestores or \tracingcommands; it makes no

difference which, as long as they both come first. This is because those two are the only tracing switches that produce output while tracing the command \conditionally@traceoff itself (see example on page 3).

In principle we would need to test the @tracing switch to see if there is anything to turn off; after all, this is the conditional trace off. However this would lead to extra output if we are currently tracing so we skip the test and instead accept that in case we are not doing any tracing we unnecessarily set the tracing primitives back to zero (i.e., the value they already have).

25 \def\conditionally@traceoff{%
26  \tracingrestores\z@
27  \tracingcommands\z@
28  \tracingpages\z@
29  \tracingmacros\z@
30  \tracingparagraphs\z@

As remarked above there are more tracing switches set by \traceon, however there is no point in resetting \tracingstats or \tracinglostchars so we leave them alone.

31 % \tracingstats\z@
32 % \tracinglostchars\z@

Since this is the command that only conditionally turns off tracing we do not touch the @tracing switch. This way a \conditionally@traceon will be able to turn the tracing on again.

33 }

That covers the case for the standard TEX program. If \tracingsgroups was defined we assume that we are running with ε-TEX in extended mode.

34 \else

In that case \traceon does more than \tracingall: it also turns on tracing of assignments and tracing of grouping.^{2} To keep tracing at a minimum \tracingassigns should be turned on last (in fact like before we disassemble \tracingall and reorder it partially).

2These are my personal preference settings; ε-TEX does in fact offer some more tracing switches and perhaps one or or more of them should be added here as well.

35 \def\traceon{%
36  \@tracingtrue
37  \tracingstats\tw@
38  \tracingpages\@ne
39  \tracinglostchars\@ne
40  \tracingparagraphs\@ne
41  \errorcontextlines\maxdimen
42  \tracingoutput\@ne
43  \showboxbreadth\maxdimen
44  \showboxdepth\maxdimen
45  \errorstopmode
46  \tracingmacros\tw@
47  \tracinggroups\@ne
48  \tracingrestores\@ne
49  \tracingcommands\tw@
50  \tracingassigns\@ne
51  \tracingonline\tracingonline@p

52 }

When turning tracing off again we now also have to turn off those additional tracing switches. But what to turn off in what order? Since \tracingassigns is quite noisy (two lines of output per assignment) and the whole command expansion consists of assignments, we had best start with this switch and follow it again by

\tracingrestores and \tracingcommands. The rest can be in any order, it

doesn’t make a difference.

With the same reasoning as before we omit testing for the @tracing switch and always set the primitives back to zero.

53 \def\conditionally@traceoff{%
54  \tracingassigns\z@
55  \tracingrestores\z@
56  \tracingcommands\z@
57  \tracingpages\z@
58  \tracingmacros\z@
59  \tracingparagraphs\z@
60  \tracinggroups\z@

61 }

This concludes the part that depends on the formatter being used.

62 \fi
\traceoff \conditionally@traceon Above we have defined \conditionally@traceoff and \traceon so now we have

to define their counterparts.

To stop tracing unconditionally we call \conditionally@traceoff (which in reality is far from conditional except for not setting the @tracing switch :-) and then reset the @tracing switch to false.

63 \def\traceoff{\conditionally@traceoff \@tracingfalse}

Now the \conditionally@traceon command will look at the @tracing switch and if it is true it will call \traceon to restart tracing (note that the latter command unnecessarily sets the switch to true as well). The reason for the \expandafter is to get rid of the \fi primitive which would otherwise show up in the tracing output (and perhaps puzzle somebody).

64 \def\conditionally@traceon{\if@tracing \expandafter \traceon \fi}

The rest of the package now consists of redefinitions of certain commands to

make use of \conditionally@traceoff.

3.1 Taming calc

\calc@open Near the start of parsing a calc expression the macro \calc@open is called. Since it already involves a group it is perfectly suitable for our task—we don’t even have to restart the tracing as this is done automatically for us.

65 \def\calc@open({\begingroup
66 \conditionally@traceoff
67 \aftergroup\calc@initB
68 \begingroup\aftergroup\calc@initB
69 \calc@pre@scan}

3.2 Making NFSS less noisy

\define@newfont Whenever NFSS determines that the font currently asked for is not already loaded, it will start looking through font definition files and then load the font. This results in a very large number of tracing lines which are not normally of interest (unless there is a bug in that area—something we hope should have been found by now). Again the code already contains its own group so we only have to turn the tracing off.

70 \def\define@newfont{%
71 \begingroup
72  \conditionally@traceoff
73  \let\typeout\@font@info
74  \escapechar\m@ne
75  \expandafter\expandafter\expandafter
76  \split@name\expandafter\string\font@name\@nil
77  \try@load@fontshape % try always
78  \expandafter\ifx
79  \csname\curr@fontshape\endcsname \relax
80  \wrong@fontshape\else
81  \extract@font\fi
82 \endgroup}

\frozen@everymath \frozen@everydisplay At the beginning of every math formula NFSS will check whether or not the math fonts are properly set up and if not will load whatever is needed. So we surround that part of the code with \conditionally@traceoff and \conditionally@traceon thereby avoiding all this uninteresting output.

83 \frozen@everymath =
84 {\conditionally@traceoff \check@mathfonts \conditionally@traceon
85  \the\everymath}
86 \frozen@everydisplay =
87 {\conditionally@traceoff \check@mathfonts \conditionally@traceon
88  \the\everydisplay}

4 Checking for italic corrections

\maybe@ic@ When executing \textit or its friends, L^{A}TEX looks ahead to determine whether or not to add an italic correction at the end. This involves looping through the \nocorrlist which outputs a lot of tracing lines we are normally not interested in. So we disable tracing for this part of the processing.

89 \def \maybe@ic@ {%
90 \ifdim \fontdimen\@ne\font>\z@
91 \else
92  \conditionally@traceoff
93  \@tempswatrue
94  \expandafter\@tfor\expandafter\reserved@a\expandafter:\expandafter=%
95  \nocorrlist
96  \do \t@st@ic
97  \if@tempswa \sw@slant \fi
98  \conditionally@traceon
99 \fi

100 }

101 ⟨/package⟩