39

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.

3
  • 2
    On the 'does TeX keep a track' part of the question: no.
    – Joseph Wright
    Jan 8, 2011 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 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. Feb 25, 2011 at 9: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. Apr 3, 2011 at 12:30

5 Answers 5

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.

10
  • I'm sorry this answer is a bit late.
    – Leo Liu
    Jan 8, 2011 at 18:40
  • 3
    I'd still call this quite quick. Jan 8, 2011 at 21:36
  • That's beautiful!
    – Thomas
    Jan 10, 2011 at 22:25
  • 1
    fixed for filehook v0.4 or later.
    – Leo Liu
    Feb 25, 2011 at 5:08
  • 1
    Nice answer. I'm planning to put something like this into my latexdef tool. Mar 21, 2011 at 15:33
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 }
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.

2
  • Thanks, that helped! "Huge" in this case was only 80 MB, nothing my computer couldn't handle.
    – Thomas
    Jan 8, 2011 at 18:17
  • I find a better answer (see below). Since this one has been accepted, it remains unchanged.
    – Leo Liu
    Jan 8, 2011 at 19:14
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
+150

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .