40

Using \@ifdefined (in LaTeX), we can check whether a command is defined.

Using \meaning, we can get the definition of a command.

Can we also get information about where a command was defined? For example, in terms of the source file and line number? Does TeX even keep track of this information?

We can grep through source files, but with constructs like \csname, there's no guarantee that the definition will show up.

Stefan Kottwitz
  • 231,401
Thomas
  • 708
  • 3
    On the 'does TeX keep a track' part of the question: no. – Joseph Wright Jan 08 '11 at 17:33
  • 1
    @Joseph: Although it is technically feasible to change the underlying engine to do this. I think that in Luatex, you can apply the techniques in http://www.ntg.nl/maps/35/05.pdf to write a meta-interpreter for Tex that evaluates tokens in the usual way but wraps code around \defs to log them appropriately to a Lua table. You could then rebuild Latex, tex4ht-style, so that all macros have this information. I'm curious to see if I can do this: I've not really got the hang of token processing in Luatex yet, and it seems like a suitable challenge. – Charles Stewart Feb 25 '11 at 09:11
  • @Charles: I believe that it might be possible to do that in TeX only. Redefine \def, \edef, \xdef, \gdef starting with \def\dummy{} to get the prefixes; then grab function and argument with \grabfargs defined as \def\grabfargs#1#2#{...}; then the replacement text and do the definition, logging whatever you want. Problems: this loses \afterassignment (we can replace by storing with \let), and treats \global\def as \def, so we need to redefine \global as something like \@globaltrue\afterassignment\afterglobal with suitable definitions. Not infeasible. – Bruno Le Floch Apr 03 '11 at 12:30

5 Answers5

43

Oh, I find a much better way to do this, also with help of filehook (the code is somewhat tricky):

\documentclass{article}
\usepackage{filehook,currfile}
\newwrite\finder
\immediate\openout\finder=\jobname.fnd

\def\searchmacro#1{
  \AtBeginOfFiles{%
    \ifdefined#1
      \expandafter\def\csname \currfilename:found\endcsname{}%
    \fi}
  \AtEndOfFiles{%
    \ifdefined#1
      \unless\ifcsname \currfilename:found\endcsname
        \immediate\write\finder{found in '\currfilename'}%
    \fi\fi}}

\searchmacro\url

\usepackage{hyperref}
\begin{document}
dummy
\end{document}

After compiling, we will get

found in 'url.sty'
found in 'hyperref.sty'

in \jobname.fnd. That is to say, \url is defined in url.sty, which is inputed by hyperref.sty.

Leo Liu
  • 77,365
13

Leo's answer using filehook and currfile brought me to the idea to include these technique into v1.2 of the latexdef tool (Perl script). It will report the first package which defines the given macros.

Example:

Shows the definition and the location of \relax, \toprule and \includegraphics after loading the 'booktabs' and 'graphicx' packages:

# latexdef -p booktabs -p graphicx -f relax toprule includegraphics
\relax is defined by (La)TeX.

\relax:
\relax

\toprule first defined in "booktabs.sty".

\toprule:
macro:->\noalign {\ifnum 0=`}\fi \@aboverulesep =\abovetopsep \global \@belowrulesep =\belowrulesep \global \@thisruleclass =\@ne \@ifnextchar [{\@BTrule }{\@BTrule [\heavyrulewidth ]}

\includegraphics first defined in "graphics.sty".

\includegraphics:
macro:->\@ifstar {\Gin@cliptrue \Gin@i }{\Gin@clipfalse \Gin@i }
Martin Scharrer
  • 262,582
10

How about use trace package and seek the macro in the log file? filehook package can help us to add the file name tag into the log file. For example:

\usepackage[logonly]{trace}
\usepackage{filehook}
\AtBeginOfFiles{\wlog{INFILE:<#1>}}
\AtEndOfFiles{\wlog{OUTFILE:<#1>}}
\traceon
\usepackage{amsmath}
\traceoff

Then we can find that \subarray is defined in amsmath.sty. Since

{into \subarray=\long macro:#1->\vcenter \bgroup \Let@ \restore@math@cr \ETC.}

appears after INFILE:<amsmath.sty>, but does not after OUTFILE:<amsmath.sty>. This needs some manual work, I use vim to search the patterns.

Warning: this may produce an extreamly huge log file. Most of time, I think a simple bisection method can work for this.

Leo Liu
  • 77,365
6
  • If you know which classes/packages to search in, then the latexdef tool is a good choice.
  • If you don't know, and your document loads lots of packages and/or is divided into subfiles that define macros, then Leo's currfile solution is helpful.

However, those solutions will only find the first definition, which may not help if the macro you are looking for is redefined with a different definition later.

Here I build on Leo's solution to report online (as in, on the terminal and pausing execution with \show, rather than saving to another file) each file in which the definition of your target macro(s) gets changed, wrapped in a package reportmacrodefn.sty:

\ProvidesPackage{reportmacrodefn}

\RequirePackage{currfile}

\def\reportmacrodefn#1{%
    \ifdefined #1
        \edef\foundmacrodefn{^^J^^J\space'\string #1' is already defined^^J\space\space\space\space (with meaning \meaning #1),^^J\space\space\space\space(i.e. it is defined before executing the '\string\reportmacrodefn' line, possibly earlier in the document, in the format file or as a primitive.)^^J\space\space\space\space Will look for re-definitions}%
        \show\foundmacrodefn
    \else
        \typeout{** Looking for definitions of \string #1...}%
    \fi
    \expandafter\let\csname macrodefn\string #1\endcsname #1%
    \AtBeginOfFiles{%
        \expandafter\let\csname\currfilename/ macrodefn\string #1\endcsname #1%
    }%
    \AtEndOfFiles{%
        \begingroup\expandafter\endgroup\expandafter\let\expandafter\reportmacrodefntmp\csname\currfilename/ macrodefn\string #1\endcsname% avoid \relax side-effect of \csname...\endcsname
        \unless\ifx\reportmacrodefntmp #1% check if the macro has changed within file \currfilename
            \begingroup\expandafter\endgroup\expandafter\let\expandafter\reportmacrodefntmpreported\csname macrodefn\string #1\endcsname
            \unless\ifx\reportmacrodefntmpreported #1%check if the macro has changed since the last time we reported it
                % don't report a line number as this only gets executed at the end of the input file so would be useless and misleading
                \edef\foundmacrodefn{^^J^^J\space'\string #1' changed^^J\space\space\space\space in '\currfilename' (file stack: \currfiledumpstack), ^^J\space\space\space\space was \meaning\reportmacrodefntmp^^J\space\space\space\space now \meaning #1}%
                \show\foundmacrodefn
                \expandafter\let\csname macrodefn\string #1\endcsname #1%
            \fi
        \fi
    }}

% currfile doesn't provide a friendly way to dump the stack, so here's one:
\ifcurrfile@abspath
\newcommand*\currfiledumpstack{%
  \expandafter\currfile@dumpstack\currfile@stack\stopquark\stopquark\stopquark\stopquark
}
\def\currfile@dumpstack#1#2#3#4{%
  \ifx#1\stopquark\@empty
  \else
  {#2\ifx\empty#3\empty\else.#3\fi}%
  \expandafter\currfile@dumpstack
  \fi
}
\else
\newcommand*\currfiledumpstack{%
  \expandafter\currfile@dumpstack\currfile@stack\stopquark\stopquark\stopquark
}
\def\currfile@dumpstack#1#2#3{%
  \ifx#1\stopquark\@empty
  \else
  {#2\ifx\empty#3\empty\else.#3\fi}%
  \expandafter\currfile@dumpstack
  \fi
}
\fi

Here's a test document to illustrate how to use the package (I called it reportmacrodefntest.tex):

\RequirePackage{reportmacrodefn}
\reportmacrodefn\[
\reportmacrodefn\text

\documentclass{article}
\usepackage{fixltx2e}
\usepackage{amsmath}

\begin{document}
See the terminal output during compilation, or the log file.
\end{document}

And the relevant output here is:

 '\[' is already defined
    (with meaning macro:->\relax \ifmmode \@badmath \else \ifvmode \nointerlineskip \makebox [.6\linewidth ]{}\fi $$\fi ),
    (i.e. it is defined before executing the '\reportmacrodefn' line, possibly earlier in the document, in the format file or as a primitive.)
    Will look for re-definitions.

 '\[' changed
    in 'fixltx2e.sty' (file stack: {reportmacrodefntest.tex}{}), 
    was macro:->\relax \ifmmode \@badmath \else \ifvmode \nointerlineskip \makebox [.6\linewidth ]{}\fi $$\fi 
    now macro:->\x@protect \[\protect \[  .

 '\text' changed
    in 'amstext.sty' (file stack: {amsmath.sty}{reportmacrodefntest.tex}{}), 
    was undefined
    now macro:->\protect \text  .

A couple of features of this solution that are worthy of note:

  • Both fixltx2e and amsmath provide the same (one-level) definition of \[, and only the first one is reported.
  • The \text macro is defined in amstext.sty, which was loaded by amsmath, and the definition is only reported once in the correct file, and the file stack is summarised too.

Of course, one still has to resort to grep or other approaches to find out where in the named file(s) the definition(s) are.

If the definition you are looking for is in the format file, then you should look in latex.ltx etc as suggested by Herbert, and the --source option to latexdef may be helpful.

Finally, this doesn't help if you're looking for something defined by the reporting code!

3

All basic LaTeX commands are defined in files which are part of the base directory, which is at $TEXMF/tex/latex/base. If a command isn't found there, it is a so called primitive of TeX or defined in one of the additional loaded packages. The main basic files are latex.ltxand fontmath.ltx