28

Below is a short program that can use \meaning and \string to let a combination of LaTeX and TeX commands to print out the listing of macros. Unsurprisingly (nothing can surprise me with TeX any more), the command can also list its own listing.

\makeatletter
\parindent0pt


\def\reflect{\@star@or@long\accommand}
\fboxrule=0.0pt

\def\accommand#1{\framebox[4cm][l]{\color{red} 
     {\tt\string#1:}} %
     \parbox[t]{7cm}{\tt\expandafter\strip@prefix\meaning#1}} 

\reflect{\reflect}\\
\reflect{\listoffigures}\\
\reflect{\section}\\
\reflect*{\thispagestyle}\\
\makeatother

I am contemplating to turn this into a package, where one can say

\reflect{command1, command2, command3} and obtain a listing of the control sequences. Am I duplicating any package you know? Is there another approach than the above? Code fails in mathmode for example \reflect{\gamma}.

yannisl
  • 117,160
  • Interesting. When I need to see the definition of a macro I just use \show to have it output in the console. – Matthew Leingang Nov 01 '10 at 15:21
  • @Matthew Leingang I use \show also, but on large packages, I prefer to print as I go along for debugging. – yannisl Nov 01 '10 at 15:25
  • I hadn't heard of the \meaning control sequence before. I tried \reflect{meaning} and I got an error. Is it primitive? – Matthew Leingang Nov 01 '10 at 15:43
  • \meaning is primitive (TexBook Chapter 20). The \reflect macro still needs work! Try \reflect{\TeX}! – yannisl Nov 01 '10 at 16:19
  • 4
    cute. two comments: (1) i think of "reflect" as what happens in a mirror, and have used that name to mirror horizontally symbols or strings using the graphics package (which already has a \reflectbox command). i'd call this something different, maybe \unwrap or \unravel. (2) maybe i assumed too much, but i tried to run this directly, without wrapping it with a document class and \begin/\end{document}, ... and failed. guess i'm spoiled, but i like examples that run right out of the box. – barbara beeton Nov 04 '10 at 21:13
  • @barbara beeton Thanks for the comments \unravel sounds good! – yannisl Nov 05 '10 at 03:25
  • 8
    I might not be in the target audience, but I don't see this being very useful to me. (It's certainly fun to experiment with, however.) I usually find that when I need to inspect macro definitions, the lack of indentation and line breaks in the output of \show and \meaning limit their utility in many cases. – Will Robertson Nov 07 '10 at 14:08
  • @Will thanks for the comment. It is just a small hack, I find it useful, for the exact reason you have mentioned, in that I can type a few commands and read the output. I have been re-reading the latex source2e and it is quicker for me to peek into the commands this way. I will post an update in a couple of days. – yannisl Nov 07 '10 at 14:47
  • 4
    I tend to concur with Will, as far as heavy debugging is concerned. And for quick debugging, I prefer to run TeX interactively and use \show to see the output directly on the terminal. – mpg Nov 07 '10 at 14:58
  • @Will: Could you post something like your comment as an answer, so that we can vote it up and get this question off the unanswered list? – Charles Stewart Dec 01 '10 at 21:12

2 Answers2

18

EDIT: a few weeks ago, I wrote this "answer", wishing for something more powerful than Yiannis did, namely, the ability of executing step by step the LaTeX file. I have a prototype. See the bottom of this post for the full code.

EDIT 2,3,4: improved the code.

I am not sure that this should be an answer, but it is definitely too long for a comment.

I would find it neat if I could type \unravel{\section*{title}} and see the macro unravel: first, expand it once, then expand the first of these macros once and show me the resulting token list, and continue expanding in the order that TeX would do it, until there is a need to read something after the token list given as an argument to \unravel.

For instance, \unravel{\section*} should give

\@startsection {section}{1}{\z@ }{-3.5ex \@plus -1ex \@minus 
-.2ex}{2.3ex \@plus .2ex}{\normalfont \Large \bfseries }*

notice the star at the end. Then it should give

\if@noskipsec \leavevmode \fi \par \@tempskipa -3.5ex \@plus -1ex 
\@minus -.2ex\relax \@afterindenttrue \ifdim \@tempskipa <\z@ 
\@tempskipa -\@tempskipa \@afterindentfalse \fi \if@nobreak 
\everypar {}\else \addpenalty \@secpenalty \addvspace \@tempskipa 
\fi \@ifstar {
  \@ssect {\z@ }{-3.5ex \@plus -1ex \@minus -.2ex}{2.3ex \@plus 
  .2ex}{\normalfont \Large \bfseries }
}{
  \@dblarg {\@sect {section}{1}{\z@ }{-3.5ex \@plus -1ex \@minus 
  -.2ex}{2.3ex \@plus .2ex}{\normalfont \Large \bfseries }}
}*

etc. It would also be neat to be able to skip the expansion of some commands: if I already know very well what \@sect does, I should be able to happily do several steps of the expansion at that point.

EDIT (cont.): Sorry, the code is quite big (and has bugs). I tried to include some comments. In the end, you just write \debug@unravel{\textrm{Hello}} to see TeX at work.

\documentclass{article}
\usepackage{trace}
\makeatletter

% Storing a few primitives that we use, to avoid crashes if they are
% redefined by the user. [...]

% =========== Setup.
% "todo": tokens that are going to be read, expanded, etc.
% "modifiers": a place to save modifiers (so far, only "\global")
% "done": the output, which in the best possible world would contain
%   tokens that produce the same output as the original ones.
\newtoks\debug@toks@todo
\long\xdef\debug@modifiers@tl{}
\newtoks\debug@toks@done
\long\gdef\debug@afterassignment@token{}


\long\gdef\debug@toks@done@append#1{%
  \global\debug@toks@done\expandafter{\the\debug@toks@done#1}%
}


% Various scratch objects.
\newcount\debug@tmp@count
\newskip\debug@tmp@skip
\newbox\debug@tmp@box

% A few parameters to control display:
\newcounter{debug@output@stream} \setcounter{debug@output@stream}{16}
\newcounter{debug@prompt@stream} \setcounter{debug@prompt@stream}{-1}
\newcounter{debug@noise}         \setcounter{debug@noise}{1}
\newcounter{debug@steps}         \setcounter{debug@steps}{0}
\newcounter{debug@nonstop}       \setcounter{debug@nonstop}{1}

% Spaces make \debug@unravel{...} choke!
%
% Explicit braces which start a group (rather than the argument
% of a macro) are not yet implemented.
% 
% \noexpand, \afterassignments, \aftergroup,
% \unhbox, \unvbox, \unhcopy, \unvcopy,
% \write, 
% ...
% need to be implemented



% The main command, \debug@unravel, simply sets the toks \debug@toks@todo,
% and repeatedly does one step of the expansion until we reach the
% end of the list.
\long\gdef\debug@unravel#1{%
  \debug@print@welcome%
  % 
  \global\debug@toks@todo{#1}%
  \global\debug@toks@done{}%
  \global\let\debug@head\relax%
  % 
  \loop%
  \debug@set@to@head\debug@head\debug@toks@todo%
  \unless\ifx\debug@head\debug@qstop%
  \debug@step%
  \repeat%
  % 
  \debug@print@outcome%
}



\long\gdef\debug@insurance#1{%
  \begingroup\escapechar=`\\\relax%`
  #1%
  \endgroup%
}


\long\gdef\debug@step{%
  % 
  % Then, in a safe environment (\escapechar=92),
  % - analyse the token,
  % - print the token list
  % - print/show the first token
  % - figure out whether we should be using \debug@type or @iv 
  % - prepare the message on what we will be doing
  \debug@insurance{%
    \debug@type@from@meaning\debug@head%
    \debug@print@done%
    \debug@print@todo%
    \debug@print@first%
    \debug@prompt%
    \debug@type@find@correct%
    \debug@setup@print@wedid%
  }%
  %
  % Finally, do what we should do.
  \csname debug@do@\debug@type@correct\endcsname%
  \debug@print@wedid% auto-insured
}

\long\gdef\debug@type@find@correct{%
  \@ifundefined{debug@do@\debug@type}{%
    \@ifundefined{debug@do@\debug@type@iv}{%
      \debug@read@unsafe{1}%
      \debug@typeout{Warning: Unknown token \debug@solid@name.}%
      \debug@typeout{\@spaces Input code to insert here. Or press return to proceed:}%
      \debug@typeout{\@spaces this dumps \debug@solid@name into the output,}%
      \debug@typeout{\@spaces by inserting \noexpand\debug@primitive{\debug@type}{unsupp}}%
      \debug@prompt@{\debug@type@find@correct@ifempty}%
      \debug@type@find@correct%
    }{%
      \global\let\debug@type@correct\debug@type@iv%
    }%
  }{%
    \global\let\debug@type@correct\debug@type%
  }%
}

\long\gdef\debug@type@find@correct@ifempty#1{%
  \ifx\debug@empty@short#1%
  \debug@primitive{\debug@type}{unsupp}%
  \fi
  #1}

% short, not long!
\gdef\debug@empty@short{}
\long\gdef\debug@empty@long{}

% ========== Macros to read the beginning of \debug@toks@todo.
\long\gdef\debug@qstop{\debug@qstop}
\global\let\debug@varqstop\debug@qstop
\long\gdef\debug@to@stop#1\debug@qstop{}
\long\gdef\debug@to@stop@keep#1\debug@qstop{#1}
\long\gdef\debug@set@to@head#1#2{%
  \expandafter\futurelet\expandafter#1%
  \expandafter\debug@to@stop\the#2\debug@qstop}


% ==== below, "unsafe" means we do not check for explicit braces or #.
% \debug@read@unsafe{3} reads the 3 first (at least 1) tokens of 
% \debug@toks@todo and stores them in \debug@read@x.
\newcount\debug@read@count%
%
\long\gdef\debug@read@unsafe#1{%
  \debug@read@count #1\relax%
  \long\gdef\debug@read@x{}%
  \expandafter\debug@read@unsafe@aux\the\debug@toks@todo%
  \debug@varqstop\debug@qstop%
}
\long\gdef\debug@read@unsafe@aux#1{%
  \expandafter\long\expandafter\gdef%
  \expandafter\debug@read@x\expandafter{\debug@read@x#1}%
  \advance \debug@read@count by \m@ne%
  \ifnum\debug@read@count>0\relax%
  \expandafter\debug@read@unsafe@aux%
  \else%
  \expandafter\debug@to@stop%
  \fi%
}




% ========== Macros to understand the meaning of the head.
\long\gdef\debug@type@from@meaning#1{%
  \expandafter\debug@meaning@aux\meaning#1 \debug@qstop%
  \expandafter\debug@tfm@macro\meaning#1->\debug@qstop%
  \long\xdef\debug@type@iv{%
    \expandafter\debug@type@nondigits\debug@type\debug@qstop}%
}

\long\gdef\debug@tfm@macro#1->#2\debug@qstop{%
  \ifx\debug@qstop#2\debug@qstop%
  \else%
  \long\gdef\debug@type{macro}%
  \fi}

\begingroup
\lccode`\*`\\
\lowercase{%
  \endgroup%
  \let\debug@char@bs*}

% \long\gdef\debug@iv#1#2#3#4#5\debug@qstop{#1#2#3#4}


\long\gdef\debug@type@nondigits#1{%
  \ifx\debug@qstop#1%
  \expandafter\debug@use@TF%
  \else%
  \expandafter\debug@use@FT%
  \fi%
  {}{%
    \ifnum9<1#1 % Critical space
    \expandafter\debug@use@TF%
    \else%
    \expandafter\debug@use@FT%
    \fi%
    {\debug@to@stop}%
    {#1\debug@type@nondigits}%
  }%
}

\long\gdef\debug@use@TF#1#2{#1}
\long\gdef\debug@use@FT#1#2{#2}
\long\gdef\debug@use@FF#1#2{}
\long\gdef\debug@use@F#1{}
\long\gdef\debug@gobble#1{}


\long\gdef\debug@meaning@aux#1#2 #3\debug@qstop{%
  \ifx\debug@char@bs#1%
  \long\gdef\debug@type{#2}%
  \else%
  \long\gdef\debug@type{character}%
  \fi%
}




% ============= Let us now act! =======================

% For "macros", we expand once, whether it is protected or not.
\long\gdef\debug@do@expand{%
  \debug@toks@expandfirst\debug@toks@todo}

\long\gdef\debug@toks@expandfirst#1{%
  \expandafter\expandafter\expandafter\expandafter%
  \expandafter\expandafter\expandafter\global%
  \expandafter\expandafter\expandafter\expandafter%
  \expandafter\expandafter\expandafter#1%
  \expandafter\expandafter\expandafter\expandafter%
  \expandafter\expandafter\expandafter{%
    \expandafter\expandafter\expandafter\empty%
    \the#1%
  }%
}

% Special type of expansion: conditionals, for which we wish to also
% output the outcome of the test.
\long\gdef\debug@do@expand@if#1{%
  \debug@insurance{%
    \debug@read@unsafe{#1}%
    \long\xdef\debug@print@wedid@tl{\space%
      Test: \detokenize\expandafter{\debug@read@x}= %
      \debug@read@x true \else false \fi%
    }%
  }%
  \debug@do@expand%
}


% For a few other cases, we remove the head of \debug@toks@todo
% and append it to \debug@toks@done
\long\gdef\debug@do@keep{%
  \debug@save@first\debug@use@first\debug@remove@first}
\long\gdef\debug@do@character{%
  \debug@save@first\debug@use@first\debug@remove@first}
\long\gdef\debug@do@unsupp{%
  \debug@save@first\debug@remove@first}
\long\gdef\debug@do@throw{\debug@remove@first}
\long\gdef\debug@do@justdo{%
  \expandafter\expandafter\expandafter\global%
  \expandafter\expandafter\expandafter\debug@toks@todo%
  \expandafter\expandafter\expandafter{%
    \expandafter\debug@use@F\the\debug@toks@todo}% 
  \debug@head%
}
\long\gdef\debug@do@relax{%
  \expandafter\expandafter\expandafter\global%
  \expandafter\expandafter\expandafter\debug@toks@todo%
  \expandafter\expandafter\expandafter{%
    \expandafter\debug@use@F\the\debug@toks@todo}% 
}

% For assignments, we use \afterassignment\debug@do@assign@aux
% to regain control after letting TeX do the assignment.
\long\gdef\debug@do@assign@after{%
  \long\xdef\debug@modifiers@tl{}%
  \let\debug@tmpa\debug@afterassignment@token%
  \let\debug@afterassignment@token\debug@empty@long%
  \global\debug@toks@todo\expandafter\expandafter\expandafter{%
    \expandafter\debug@tmpa\iffalse}\fi%
}
\long\gdef\debug@do@assign{%
  \afterassignment\debug@do@assign@after%
  \iffalse{\fi%
    \expandafter\debug@modifiers@tl%
    \the\debug@toks@todo}%
}

% Todo: reset modifiers after any step
\long\gdef\debug@do@modifier{%
  \debug@read@unsafe{1}%
  \long\xdef\debug@modifiers@tl{%
    \debug@modifiers@tl\debug@read@x}%
  \debug@remove@first%
}

% Experimental \afterassignment.
\long\gdef\debug@do@afterassignment{%
  \debug@remove@first%
  \debug@read@unsafe{1}%
  \debug@remove@first%
  \let\debug@afterassignment@token\debug@read@x%
}


% When we want to keep things like skips, penalties, etc., and
% particularly cleanup after them, we assign their "argument" to 
% an internal skip or count, and regain control \afterassignment.
% We use \debug@do@keep@skip and \debug@do@keep@count.
\long\gdef\debug@remember#1{\long\gdef\debug@remembered{#1}}

\long\gdef\debug@do@keep@after{%
  \long\xdef\debug@modifiers@tl{}%
  \global\debug@toks@todo\expandafter{\iffalse}\fi%
}

\long\gdef\debug@do@keep@#1{%
  \long\gdef\debug@tmpa{\afterassignment\debug@do@keep@after%
    \csname debug@tmp@#1\endcsname}%
  \afterassignment\debug@tmpa%
  \iffalse{\fi\expandafter\debug@remember\the\debug@toks@todo}%
  % 
  \expandafter\debug@remembered\the\csname debug@tmp@#1\endcsname%
  %
  \expandafter\expandafter\expandafter\debug@toks@done@append%
  \expandafter\expandafter\expandafter{%
    \expandafter\debug@remembered%
    \the\csname debug@tmp@#1\endcsname%
    \relax}%
}

\long\gdef\debug@do@keep@count{\debug@do@keep@{count}}
\long\gdef\debug@do@keep@skip{\debug@do@keep@{skip}}


\long\gdef\debug@do@keep@count@edef{%
  \long\gdef\debug@tmpa{% captures the rest of the toks
    \global\debug@toks@todo\expandafter{\iffalse}\fi%
  }%
  \long\gdef\debug@tmpb{% captures the text to write, then proceed
    \afterassignment\debug@tmpa%
    \edef\debug@tmpw% will get the text to write
  }%
  \long\gdef\debug@tmpc{% captures the stream number, then proceed
    \afterassignment\debug@tmpb%
    \global\debug@tmp@count% will get the stream.
  }%
  \afterassignment\debug@tmpc%
  \iffalse{\fi\expandafter\debug@remember\the\debug@toks@todo}%
  % 
  \long\xdef\debug@tmpa{%
    \debug@modifiers@tl%
    \expandafter\noexpand\debug@remembered%
    \the\debug@tmp@count%
    {\unexpanded\expandafter{\debug@tmpw}}%
  }%
  \debug@tmpa%
  \expandafter\debug@toks@done@append\expandafter{\debug@tmpa}%
  \long\xdef\debug@modifiers@tl{}%
}



% For \setbox, the situation is more complicated: \afterassignment only
% brings us inside the box. So we \afterassignment a macro which expands
% to \aftergroup \<regain_control>.

\long\gdef\debug@do@assign@after@afgr{\aftergroup\debug@do@assign@after}
\long\gdef\debug@do@assign@box{%
  \afterassignment\debug@do@assign@after@afgr%
  \iffalse{\fi%
    \the\debug@toks@todo}%
}


% For \hbox, \vbox, \vtop, we cheat by storing them inside the dummy 
% box \debug@tmp@box (using \savebox that we have just obtained).
\long\gdef\debug@do@any@box{%
  \global\debug@toks@todo\expandafter{%
    \expandafter\setbox\expandafter\debug@tmp@box\expandafter=%
    \the\debug@toks@todo}%
}



% ===== Moving the first from \debug@toks@todo to \debug@toks@done.

\long\gdef\debug@remove@first{%
  \expandafter\expandafter\expandafter\global%
  \expandafter\expandafter\expandafter\debug@toks@todo%
  \expandafter\expandafter\expandafter{%
    \expandafter\debug@use@F\the\debug@toks@todo}% 
}

\long\gdef\debug@save@first{%
  \expandafter\debug@save@first@\the\debug@toks@todo\debug@qstop}
\long\gdef\debug@save@first@#1{%
  \debug@toks@done@append{#1}%
  \debug@to@stop}

\long\gdef\debug@use@first{%
  \expandafter\debug@use@first@\the\debug@toks@todo\debug@qstop}
\long\gdef\debug@use@first@#1{%
  \expandafter#1\debug@to@stop}




% ============ Setup one macro per primitive

\long\gdef\debug@primitive#1{%
  \long\xdef\debug@tmpa@tl{#1}%
  \debug@primitive@aux%
}
\long\gdef\debug@primitive@iv#1{%
  \long\xdef\debug@tmpa@tl{\debug@type@nondigits #1\debug@qstop}%
  \debug@primitive@aux%
}
\long\gdef\debug@primitive@aux#1{%
  \long\xdef\debug@tmpb@tl{%
    \unexpanded\expandafter\expandafter\expandafter{%
      \csname debug@text@do@#1\endcsname}}%
  \expandafter\global\expandafter\let%
  \csname debug@do@\debug@tmpa@tl\expandafter\endcsname%
  \csname debug@do@#1\endcsname%
  \expandafter\long\expandafter\gdef\csname debug@text@do@\debug@tmpa@tl%
    \expandafter\endcsname\expandafter{\debug@tmpb@tl}%
}


\long\gdef\debug@solid@name{\detokenize\expandafter{\debug@read@x}}
\long\gdef\debug@text@do@character{Output the character \debug@solid@name. }
\long\gdef\debug@text@do@expand{Expanded \debug@solid@name once. }
\long\gdef\debug@text@do@keep{Sent \debug@solid@name to the output. }
\long\gdef\debug@text@do@justdo{Just did \debug@solid@name! }
\long\gdef\debug@text@do@throw{Ignored \debug@solid@name! }
\long\gdef\debug@text@do@relax{Relaxed a bit with \debug@solid@name. }
\long\gdef\debug@text@do@keep@skip{Output \debug@solid@name and its skip. }
\long\gdef\debug@text@do@keep@count{Output \debug@solid@name and its count. }
\long\gdef\debug@text@do@keep@count@edef{Maybe it was a write? \debug@solid@name. }
\long\gdef\debug@text@do@unsupp{Unsupported \debug@solid@name dumped into the output. }
\long\gdef\debug@text@do@assign{Assigned using %
  \detokenize\expandafter{\debug@modifiers@tl}\debug@solid@name... }
\long\gdef\debug@text@do@assign@box{Assigned a box using \debug@solid@name... }
\long\gdef\debug@text@do@any@box{Preparing to store the \debug@solid@name in our box. }
\long\gdef\debug@text@do@modifier{Remembering the modifier \debug@solid@name. }
\long\gdef\debug@text@do@afterassignment{Attempting to take care of afterassignment.}

% In the current implementation, macros are mis-recognized as
% their modifiers, so we need to cheat and pretend that those
% modifiers should just be expanded. This will fail if there
% is a genuine \long or \protected or \outer out there.

\debug@primitive{immediate}{modifier}
\debug@primitive{global}{modifier}
%\debug@primitive{outer}{modifier}%disabled because it will break me.
\debug@primitive{long}{modifier}
\debug@primitive{protected}{modifier}

\debug@primitive{macro}{expand}
\debug@primitive{expandafter}{expand}
\debug@primitive{noexpand}{expand}
\debug@primitive{detokenize}{expand}
\debug@primitive{string}{expand}
\debug@primitive{csname}{expand}
\debug@primitive{lowercase}{expand}
\debug@primitive{uppercase}{expand}

\debug@primitive{relax}{relax}
\debug@primitive{par}{keep}
\debug@primitive{indent}{keep}
\debug@primitive{unskip}{keep}
\debug@primitive{unhbox}{keep@count}
\debug@primitive{unhcopy}{keep@count}
\debug@primitive{penalty}{keep@count}
\debug@primitive{vskip}{keep@skip}
\debug@primitive{hskip}{keep@skip}
\let\debug@do@muskipAA\debug@do@keep@muskip% not yet supported!


\debug@primitive{begingroup}{keep}
\debug@primitive{endgroup}{keep}
\debug@primitive{write}{keep@count@edef}

\debug@primitive{ignorespaces}{unsupp}
\debug@primitive{unpenalty}{unsupp}
\debug@primitive{/}{unsupp}
\debug@primitive{hbox}{unsupp}
\debug@primitive{vbox}{unsupp}
\debug@primitive{afterassignment}{afterassignment}
\debug@primitive{aftergroup}{throw}


\debug@primitive{futurelet}{assign}
\debug@primitive{let}{assign}
\debug@primitive{def}{assign}
\debug@primitive{edef}{assign}
\debug@primitive{gdef}{assign}
\debug@primitive{xdef}{assign}
\debug@primitive{advance}{assign}
\debug@primitive@iv{toks}{assign}
\debug@primitive@iv{skip}{assign}
\debug@primitive@iv{font}{assign}
\debug@primitive@iv{muskip}{assign}
\debug@primitive@iv{dimen}{assign}
\debug@primitive@iv{count}{assign}
\debug@primitive{everypar}{assign}
\debug@primitive{baselineskip}{assign}
\debug@primitive{parfillskip}{assign}
\debug@primitive{parskip}{assign}
\debug@primitive{leftskip}{assign}
\debug@primitive{rightskip}{assign}
\debug@primitive{parindent}{assign}
\debug@primitive{parshape}{assign}
\debug@primitive{escapechar}{assign}
\debug@primitive{hyphenchar}{assign}
\debug@primitive{catcode}{assign}
\debug@primitive{lccode}{assign}
\debug@primitive{uccode}{assign}
\debug@primitive{accent}{assign}%??
\debug@primitive{tracingassigns}{assign}
\debug@primitive{tracingcommands}{assign}
\debug@primitive{tracinggroups}{assign}
\debug@primitive{tracinglostchars}{assign}
\debug@primitive{tracingmacros}{assign}
\debug@primitive{tracingonline}{assign}
\debug@primitive{tracingoutput}{assign}
\debug@primitive{tracingpages}{assign}
\debug@primitive{tracingparagraphs}{assign}
\debug@primitive{tracingrestores}{assign}
\debug@primitive{tracingstats}{assign}
\debug@primitive{showboxbreadth}{assign}
\debug@primitive{showboxdepth}{assign}

\debug@primitive{setbox}{assign@box}
\debug@primitive{hbox}{any@box}
\debug@primitive{vbox}{any@box}
\debug@primitive{vtop}{any@box}

\debug@primitive{if}{expand}
\debug@primitive{ifdim}{expand}
\debug@primitive{ifnum}{expand}
\debug@primitive{else}{expand}
\debug@primitive{fi}{expand}
\long\gdef\debug@do@ifx{\debug@do@expand@if{3}}
\long\gdef\debug@do@ifvmode{\debug@do@expand@if{1}}
\long\gdef\debug@do@ifhmode{\debug@do@expand@if{1}}
\long\gdef\debug@do@ifmmode{\debug@do@expand@if{1}}
\long\gdef\debug@do@iffalse{\debug@do@expand@if{1}}
\long\gdef\debug@do@iftrue{\debug@do@expand@if{1}}




% ========== Messages.
\usepackage{hardwrap}

\long\gdef\debug@typeout#1{\immediate\write\c@debug@output@stream{#1}}

\long\gdef\debug@print@welcome{%
  \debug@typeout{}%
  \debug@typeout{======== Welcome to the debug package ========}%
  \debug@typeout{\@spaces "|>" denotes tokens that we will act on.}%
  \debug@typeout{\@spaces "<|" denotes the output to TeX's stomach.}%
  \debug@typeout{\@spaces Type "\string\step{2}" to do two steps [...]}%
  \debug@typeout{}%
}

\long\gdef\debug@print@outcome{%
  \debug@typeout{Step \the\c@debug@steps\space===== The end!}%
  \debug@print@done%
}

\long\gdef\debug@message@aux#1#2#3{%
  \begingroup%
  \lccode`\*=\newlinechar\relax%
  \lowercase{\def\debug@newline{*}}%
  \def\MessageBreak{%
    \debug@newline #1\space\space}%
  \set@display@protect%
  \debug@typeout{#2\MessageBreak #3}%
  \debug@typeout{}%
  \endgroup}%

\long\gdef\debug@message#1#2#3{%
  \HardWrap{\debug@message@aux{#1}{#2}}{72}{\HardWrapSetup}%
  {\MessageBreak}{#3}}

\long\gdef\debug@print@todo{%
  \ifnum\the\c@debug@noise>-1\relax%
  \debug@message{|>}{\expandafter\debug@use@F}{%
    \detokenize\expandafter{\the\debug@toks@todo}}%
  \fi}

\long\gdef\debug@print@done{%
  \debug@insurance{%
    \debug@message{<|}{\expandafter\debug@use@F}{%
      \detokenize\expandafter{\the\debug@toks@done}}}}

\long\gdef\debug@setup@print@wedid{%
  \debug@read@unsafe{1}%
  \long\xdef\debug@print@wedid@tl{\space%
    \csname debug@text@do@\debug@type@correct\endcsname}}

\long\gdef\debug@print@wedid{%
  \debug@insurance{%
    \global\advance \c@debug@steps by 1\relax%
    \lccode`\*=\newlinechar%
    \lowercase{\long\gdef\MessageBreak{*\@spaces\@spaces\@spaces}}%
    \debug@typeout{Step \the\c@debug@steps\space%
      =====\debug@print@wedid@tl}%
    \ifnum\c@debug@noise>-1\relax%
    \debug@typeout{}%
    \fi%
  }%
}



% ============= To show the first token.

\long\gdef\debug@print@first{%
  \ifnum\the\c@debug@noise>0\relax
  \debug@read@unsafe{1}%
  \long\xdef\debug@first@meaning{\expandafter\meaning\debug@read@x}%
  \long\xdef\debug@first@string{\expandafter\string\debug@read@x}%
  \debug@message{\debug@use@FF}{\debug@use@F}{%
    \debug@first@string=\debug@first@meaning}%
  \fi%
}






% ============= The prompt
\long\def\debug@hop@else#1\else#2\fi{\fi#1}
\long\def\debug@hop@fi#1\fi{\fi#1}


\long\gdef\debug@prompt{%
  \ifnum\the\c@debug@nonstop=1\relax%
  \debug@hop@else\debug@prompt@{\debug@prompt@treat}%
  \else%
  \global\advance\c@debug@nonstop by -1\relax%
  \fi%
}

\long\gdef\debug@prompt@treat#1{%
  \expandafter\debug@prompt@treat@aux#1\debug@qstop}
\long\gdef\debug@prompt@treat@aux#1{%
  \ifcat A\noexpand#1\relax%
  \expandafter\debug@use@TF%
  \else%
  \expandafter\debug@use@FT%
  \fi%
  {\csname debug@prompt@treat@#1\endcsname%
    \debug@prompt@treat@aux}%
  {\debug@to@stop@keep#1}%
}
\long\gdef\debug@prompt@treat@q{\noise{-1}\step{0}}
\long\gdef\debug@prompt@treat@x{\endgroup\endgroup\fi\iffalse}
\long\gdef\debug@prompt@treat@s#1#2{\silentstep{#2}#1}
\long\gdef\debug@prompt@treat@o#1#2{%
  \ifnum#2<0\relax%
  \else\ifnum#2=0\relax%
  \global\c@debug@output@stream-1\relax%
  \else%
  \global\c@debug@output@stream16\relax%
  \fi\fi%
  #1}


\long\xdef\debug@prompt@tl#1{%
  \noexpand\read\noexpand\c@debug@prompt@stream to%
  \expandafter\noexpand\csname Your input \endcsname%
  #1\expandafter\noexpand\csname Your input \endcsname}
\long\gdef\debug@prompt@before@tl{}
\long\gdef\debug@prompt@#1{%
  \begingroup\escapechar=-1\relax%
  \def\step##1{\global\c@debug@nonstop##1\relax}%
  \def\noise##1{\global\c@debug@noise##1\relax}%
  \def\silentstep##1{%
    \noise{-1}%
    \step{##1}%
    \long\gdef\debug@prompt@before@tl{\noise{1}}%
  }%
  \endlinechar=-1%
  \debug@prompt@before@tl%
  \long\gdef\debug@prompt@before@tl{}%
  \debug@prompt@tl{#1}%
  \endgroup}


\usepackage{amsmath}
\begin{document}


\section{Normal-section}

Title above for comparison purposes.

\debug@unravel{\section{Unravelled-section}}%

The section title above was typeset as we unravelled.

\the\debug@toks@done%

That third section is made using what is stored at 
the end of the unravelling.

Only trivial maths works: $\debug@unravel{xy = z}$, 
and it is stored in our ouput token list, $\the\debug@toks@done$.

Some more complex maths symbols fail, complain, cheat: 
\[
\debug@unravel{\int\mathrm{d}x\sin(\pi)=0}
\]
\[
\the\debug@toks@done.
\]

And sub- and super-scripts are totally unsupported so far.

\end{document}
David Carlisle
  • 757,742
  • I agree! I think that you could theoretically generate this information from parsing the log file with \tracingall. Anyone game? :) – Will Robertson Jan 20 '11 at 11:36
  • Sounds like a nice idea. If can easily do it if there are only expandable tokens, but in your example, you get a \par after 3 expansions, and even worse, \@tempskipa -3.5ex \@plus -1ex \@minus -.2ex\relax after the \par. I'm not sure how to handle this. – Hendrik Vogt Jan 20 '11 at 12:12
  • @Bruno It sounds like a good idea, but is probably best done by parsing the log file with an external script. – yannisl Jan 20 '11 at 15:19
  • @Hendrik: see the code that I added above for a way to have unexpandable tokens without too much trouble (still plenty of primitives are not implemented, but e.g., \xdef, \let, \expandafter, TeX dimen and count assignments, \setbox, etc. are). I would be glad to have some feedback :). Should I post this on comp.text.tex for example? – Bruno Le Floch Jan 31 '11 at 14:19
  • @Bruno: I haven't had time to look at it in details, but it seems to be a great start. I can't say anything about comp.text.tex; never been there. – Hendrik Vogt Jan 31 '11 at 15:57
  • @Hendrik: I guess I should eventually make a package out of it once I get time to consolidate it. But getting to a full coverage of TeX's 300 or so primitives seems tough (some will be impossible). – Bruno Le Floch Jan 31 '11 at 17:00
  • @Bruno, a few comments: 1. this of type (macr, macroAAA) is not so nice yet, 2. you should have a look at the hardwrap package, 3. for \if tests it would be great if one would get a feedback if it was true or false. 4. There should be an option to get the (wrong) output (so far) in every step. And of course it would be great to get the correct output, at least in this example. – Hendrik Vogt Jan 31 '11 at 18:01
  • @Hendrik: thank you :). 1. Yes, I left it for my debugging purposes (to know what the code "thinks"), and forgot it there. 2. hardwrap will be useful for the display part, indeed. 3. I also thought about this, but that seems very difficult with the current approach: I completely let TeX take care of all things that can be expanded, in particular conditionals. 4. For the output, note that e.g., font changes, take effect (\defs are \defed), but it is not clear when one should also add those to the "output". It seems easier to get the (almost) right dvi/pdf. – Bruno Le Floch Jan 31 '11 at 18:37
  • @Bruno: 3. But you do test that an \if test is to be performed, don't you? Can't you simply do some \thepresentiftest true\else false\fi? – Hendrik Vogt Jan 31 '11 at 18:49
  • @Bruno: OK, sorry, this is simple only for \ifmmode. – Hendrik Vogt Jan 31 '11 at 21:48
  • @Hendrik: I did not have that idea. It works for \iffalse,\iftrue,\ifmmode, etc. It can in fact be adapted to\ifx. But for e.g.,\ifnum`, I don't see how to go. Also, I should keep track of the nesting of groups, to give the user a nicer view on things. (1) Partially fixed. (2) Now used for most outputs. (4) is getting closer. – Bruno Le Floch Feb 01 '11 at 06:10
  • @Bruno: Wow, that was a quick implementation. Was it on purpose that one doesn't have to press enter anymore to proceed? I liked the old behaviour better. Moreover, saying "Package debug" all the time is really too much. I want the information, and I want it concise, and that's what your code can do! The info between the ==== is great. (You might want to put a space between = and false for \if tests.) One more idea: In interactive mode, "s" plus enter allows the user to skip the details of an expansion. – Hendrik Vogt Feb 01 '11 at 11:16
  • @Hendrik: I had left a hook for "needing to press" vs. not needing it hidden somewhere. Now I made the interface simpler, and fixed some bugs: on the very simple example \textrm{hi}\emph{hello} above, it works, and even outputs the right thing to a file afterwards. --- Thanks to Martin Scharrer, I'm now using \read. Three user commands can be used in the prompt: \step{<positive int>}, \silentstep{<positive int>}, \noise{<-1 or 0 or 1>}. Using "0" for "positive int" will let things scroll by. --- also, \show\foo in the prompt will do the expected. – Bruno Le Floch Feb 01 '11 at 22:12
  • @Bruno: The user commands are good, but I'd also like to have the simple "q" and "x" back for "quiet" and "leave". Is that possible? Another thing: "Expanded \expandafter once" could be supplemented with the meaning of what \expandafter is going to expand. Not sure if this is a good idea, but in some cases it could be very helpful. One last remark: At the moment I don't find the output very intuitive; it took me some time to understand this |> and <|. – Hendrik Vogt Feb 02 '11 at 10:44
  • @Bruno: Great job so far! A few more things: 1. After an assignment, it would be nice if one could see the result of the assignment. 2. If, e.g., one assigns a box with \setbox, then inside the \hbox or \vbox a lot can happen, so it would be great if one could step inside. 3. The $ isn't implemented yet, so \debug@unravel{$\hat a$} doesn't work. – Hendrik Vogt Feb 02 '11 at 19:52
  • @Hendrik: I realized that to get the conditionals (\ifvmode etc) right, the material needs to be typeset as TeX goes. The new code produces the right input for \section, and a few other non-math mode commands. I also improved the error handling, so that it is possible to pretend that maths partially works (no _, ^, \frac...). When prompted, q will go to a semi-quiet mode, o{0}q will output more to the log than the terminal, x will end the current \unravel in an almost clean way. – Bruno Le Floch Feb 04 '11 at 18:49
  • Also, it will be difficult to work inside the box: this requires to spawn a similar debugging inside the box. If I was using local variables, everything would be fine.. But since grouping is controlled by the input, I can only have global variables. Thus, I need to number macros in a clever way. --- A more "pressing" issue is that of spaces and braces, which will require some work. – Bruno Le Floch Feb 04 '11 at 18:54
  • @Bruno: I'll have a closer look later; maybe even a week later. Hope I don't forget ... About spawning a similar debugging inside the box: Can't you somehow have a global counter that tells you in which box depth level you are? (As I said, I didn't have a close look so far.) – Hendrik Vogt Feb 05 '11 at 13:51
  • @Hendrik: in principle, yes. In practice, that's one more layer of code, so I want to clean up some more before I refactor the whole thing in a better way some time next month. – Bruno Le Floch Feb 05 '11 at 14:06
  • @Bruno: That sounds reasonable, yes. – Hendrik Vogt Feb 05 '11 at 14:09
  • @Bruno: Just to let you know, the 10th or 11th edit of your answer will make it community wiki, meaning that you won't earn further reputation for it. – Hendrik Vogt Feb 05 '11 at 14:19
  • 1
8

Charles has asked me to post a summary of the comments above; here's a quick synopsis.

Will:

I might not be in the target audience, but I don't see this being very useful to me. (It's certainly fun to experiment with, however.) I usually find that when I need to inspect macro definitions, the lack of indentation and line breaks in the output of \show and \meaning limit their utility in many cases.

Barbara:

cute. two comments:

  1. i think of "reflect" as what happens in a mirror, and have used that name to mirror horizontally symbols or strings using the graphics package (which already has a \reflectbox command). i'd call this something different, maybe \unwrap or \unravel.
  2. maybe i assumed too much, but i tried to run this directly, without wrapping it with a document class and \begin/\end{document}, ... and failed. guess i'm spoiled, but i like examples that run right out of the box.

Manuel:

For quick debugging, I prefer to run TeX interactively and use \show to see the output directly on the terminal.

  • 1
    Barbara's comment was especially valuable for me - especially given her many years involvement with TeX and the AMS - in that packages should still be aimed at the TeX community as well i.e., one needs to test if a LaTeX command is available and if not define it. – yannisl Dec 02 '10 at 10:51