2

I tried making a macro that checks if some macro is defined or not, and if it is it will print the definition of the macro. If the macro is undefined, it will showcase this. I am somewhat content with how it works, but I want to do some improvements. Here is a MWE of the macro:

\documentclass{article}

\newcommand{\defcheck}[1]{ \ifdefined#1 \noindent\texttt{: --> \meaning#1}\vspace{\baselineskip} \else \noindent\texttt{!: --> Undefined macro}\vspace{\baselineskip} \fi }

\begin{document}

% \section*{Macro Definitions: Output} % Logged output from \texttt{\textbackslash defcheck}:\vspace{\baselineskip}

\defcheck{\defcheck{\usepackage}}

\defcheck{\undefined}

\end{document}

Output:

enter image description here

There are 3 things I would like to fix:

1. Unfortuantly, if I do \defcheck{\usepackage} it will return an error, saying LaTeX Error: Can be used only in preamble. How can I avoid any errors inside \defcheck? Is there a way to not execute any commands that is given as an arguemt of \defcheck? I was thinking maybe expl3 syntax may solve this problem? Or maybe just give the text and in some way make it into a command where needed? (as in \defcheck{usepackage} and then insert a \ before #1 in some way to make it a command)

2. When the macro is defined the command should print the macro before the colon, like this: \defcheck : --> \long macro:#1-> \ifdefined (...) . This may be fixed with \textbacklash and the solution mentioned above (\defcheck{usepackage} instead of \defcheck{\usepackage}). In the case of an undefined macro, I would like it to be something like: ! : --> Undefined macro; "\undefined"

3. I would like to sort of "collect" all \defchecks in the same place in the document (under a section or chapter), like this:

enter image description here

Preferably this should be at the end of the PDF (on its own page), and not appear in TOC. All \defchecks should automaticually go under this section in the order they were called.


Side note: How do I get other commands printed under the same section? For example; if I were to make a similar command to check if a colour is defined, how could I print the results in the same section (but maybe with its own subsection)?


Edit: when I tried \hbox it printed : --> \hbox. Why is it unable to show the definition of \hbox? A similar thing happened when i tried \csname.

Edit 2: I see now that I may not have expressed myself clearly at number 3. If I use \defcheck in the middle of a paragraph on the first page, I still want it to appear on a separate page last in the document. Preferably, the first use of the macro would automaticually create a new page at the end of the document where all the other defcheck commands also go.

Vebjorn
  • 1,778
  • 2
    \defcheck{\usepackage} would not error, you have \defcheck{\defcheck{\usepackage}} but #1 should be a single token – David Carlisle Nov 21 '22 at 22:22
  • 2
    \hbox and \csname are primitives. – Ian Thompson Nov 21 '22 at 22:23
  • 2
    \hbox has no definition, it is a primitve – David Carlisle Nov 21 '22 at 22:23
  • your command adds three spaces everytime it is used and adds vertical space after any text on the same line \defcheck{\a} \defcheck{\b} xxx would show \a and \b then xxx then have a vertical space of 2\baselineskip ? – David Carlisle Nov 21 '22 at 23:46
  • --> Undefined macro -- If it is undefined according to \ifdefined, then it is not a macro, it's just an undefined control sequence. If it is not undefined according to \ifdefined, then it might be an explicit character token or a macro or a primitive or an unexpandable non-primitive like a \chardef-token or a \toksdef-token or an implicit character or whatever... How to handle the case of the token in question not being a control sequence at all but an explicit non-active character token? – Ulrich Diez Nov 23 '22 at 19:08

2 Answers2

4

There is no error if you remove the spurious \defcheck.

However, the output you get isn't very helpful.

\documentclass{article}

\frenchspacing

\newcommand{\defcheck}[1]{% \par \ifdefined#1% \noindent\texttt{\string#1: \meaning#1}% \else \noindent\texttt{!\string#1: Undefined macro}% \fi \par \vspace{\baselineskip} }

\begin{document}

% \section*{Macro Definitions: Output} % Logged output from \texttt{\textbackslash defcheck}:\vspace{\baselineskip}

\defcheck{\usepackage}

\defcheck{\makebox}

\defcheck{\undefined}

\end{document}

enter image description here

Can you get any information from this?

With the trick

\expandafter\defcheck\csname makebox \endcsname

you'd get the real definition of \makebox, but it's

enter image description here

and it's not really informative either.

In order to accumulate the \defcheck commands and print them at the end, you can do as follows.

\documentclass{article}

\newtoks\defchecktoks

\newcommand{\defcheck}[1]{% \global\defchecktoks=\expandafter{\the\defchecktoks\printdefcheck#1}% }

\AtEndDocument{% \section*{Macro Definitions: Output}% Logged output from \texttt{\string\defcheck}:% \par\nobreak\vspace{\baselineskip}% \frenchspacing\raggedright \the\defchecktoks }

\newcommand{\printdefcheck}[1]{% \par \ifdefined#1% \noindent\texttt{\string#1: \meaning#1}% \else \noindent\texttt{!\string#1: Undefined macro}% \fi \par \vspace{\baselineskip} }

\begin{document}

\section{Title}

Some text

\defcheck{\log}

\section{Another}

Some text

\defcheck{\mbox}

\defcheck{\undefined}

\end{document}

enter image description here

I'm still uncertain about the usefulness of such a thing.

egreg
  • 1,121,712
0

In addition to what egreg already wrote you might be interested in some tests that focus on properties that can be deduced by parsing the meaning of a macro-argument's first token:

\makeatletter
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@removespace, \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingExplicitSpace,
%% \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has a leading
%%                        explicit catcode-1-character-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked does not have a
%%                        leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
  \expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does have a
%%                                       leading explicit space-token>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does not have a
%%                                       a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
  \romannumeral\UD@CheckWhetherNull{#1}%
  {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
  {%
    % Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
    % and thus do not disturb when the test is carried out within \halign/\valign:
    \expandafter\UD@firstoftwo\expandafter{%
      \expandafter\expandafter\expandafter\UD@stopromannumeral
      \romannumeral\expandafter\UD@secondoftwo
      \string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
    }{}%
  }%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
  \long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
    {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
    {\expandafter\expandafter\expandafter\UD@stopromannumeral
     \expandafter\expandafter\expandafter}%
     \expandafter\UD@secondoftwo\expandafter{\string}%
  }%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%
%% Due to \romannumeral-expansion the result is delivered after two 
%% expansion-steps/after "hitting" \ExtractFirstArg with \expandafter
%% twice.
%%
%% \UD@ExtractFirstArg's argument must not be blank.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% I chose frozen-\relax because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
  \expandafter\expandafter\expandafter\UD@Exchange
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\ifnum0=0\fi}%
  {\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
  \romannumeral\expandafter
  \UD@PassFirstToSecond\expandafter{\romannumeral
    \expandafter\expandafter\expandafter\UD@Exchange
    \expandafter\expandafter\expandafter{%
    \expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1{}}%
  }{%
    \UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
  }%
}{%
  \newcommand\UD@ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\UD@stopromannumeral#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%%=============================================================================
%% Check whether token is macro (only with macros the \meaning contains the
%% sequence ->) - the argument of \UD@CheckWhetherMacro must be a single token;
%% the argument of \UD@CheckWhetherMacro must not be empty:
%%=============================================================================
\newcommand\UD@CheckWhetherMacro[1]{%
  \expandafter\expandafter\expandafter\UD@CheckWhetherNull
  \expandafter\expandafter\expandafter{%
  \expandafter\UD@@CheckWhetherMacro\meaning#1->}%
  {\UD@secondoftwo}{\UD@firstoftwo}%
}%
\@ifdefinable\UD@@CheckWhetherMacro{\long\def\UD@@CheckWhetherMacro#1->{}}%
%%=============================================================================
%% Check whether token is undefined control sequence (only with undefined 
%% control sequences the \meaning has the leading phrase "undefined"; besides
%% this \meaning never delivers no tokens at all) - the argument of 
%% \UD@CheckWhetherUndefined must be a single token; the argument of 
%% \UD@CheckWhetherUndefined must not be empty:
%%=============================================================================
\begingroup
\def\UD@CheckWhetherUndefined#1#2{%
  \endgroup
  \newcommand\UD@CheckWhetherUndefined[1]{%
    \expandafter\expandafter\expandafter\UD@CheckWhetherNull
    \expandafter\expandafter\expandafter{%
    \expandafter\UD@@CheckWhetherUndefined\meaning##1#1#2}%
  }%
  \@ifdefinable\UD@@CheckWhetherUndefined{%
    \def\UD@@CheckWhetherUndefined##1#1##2#2{##1}%
  }%
}%
\escapechar=-1\relax
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}{%
  \expandafter\UD@CheckWhetherUndefined\expandafter{\string\undefined}%
}%
%%=============================================================================
%% Check whether token is expandable or not (only with expandable and undefined
%% control sequences the \meaning temprarily changes to \relax when
%% "hitting with \noexpand) - the argument of \UD@CheckWhetherExpandable must
%% be a single token; the argument of \UD@CheckWhetherUndefined must not be
%% empty:
%%=============================================================================
\newcommand\UD@CheckWhetherExpandable[1]{%
  \expandafter\UD@firstoftwo\expandafter{\romannumeral
  \expandafter\ifx\noexpand#1#1%
  \expandafter\UD@stopromannumeral\expandafter\UD@secondoftwo\else
  \expandafter\UD@stopromannumeral\expandafter\UD@firstoftwo\fi}{}%
}%
%%=============================================================================
%% Check whether argument has a first token which is a macro:
%% - the test yields the false branch if the first token is a primitive or
%%   an unexpanfable non-primitive or an explicit character tken.
%% - the test yields the false-branch also if the argument is empty/contains
%%   no token at all.
%%=============================================================================
\newcommand\CheckWhetherArgumenHasFirstTokenWhichIsMacro[1]{%
  \romannumeral
  \UD@CheckWhetherNull{#1}{\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
    \UD@CheckWhetherBrace{#1}{\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
      \UD@CheckWhetherLeadingExplicitSpace{#1}{\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
        \expandafter\expandafter\expandafter\UD@CheckWhetherMacro\UD@ExtractFirstArg{#1}%
        {\expandafter\UD@stopromannumeral\UD@firstoftwo}%
        {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
      }%
    }%
  }%
}%
%%=============================================================================
%% Check whether argument has a first token which is an undefined control
%% sequence:
%% - the test yields the false-branch if the argument's first token is s.th.
%%   other than a control sequence which is undefined.
%% - the test yields the false-branch also if the argument is empty/contains
%%   no token at all.
%% (Control sequences can be active-character-tokens or control-sequence-tokens,
%%  i.e. control-word-tokens or control-symbol-tokens.)
%%=============================================================================
\newcommand\CheckWhetherArgumenHasFirstTokenWhichIsUndefined[1]{%
  \romannumeral
  \UD@CheckWhetherNull{#1}{\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
    \UD@CheckWhetherBrace{#1}{\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
      \UD@CheckWhetherLeadingExplicitSpace{#1}{\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
        \expandafter\expandafter\expandafter\UD@CheckWhetherUndefined\UD@ExtractFirstArg{#1}%
        {\expandafter\UD@stopromannumeral\UD@firstoftwo}%
        {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
      }%
    }%
  }%
}%
%%=============================================================================
%% Check whether argument has a first token which is an expandable primitive:
%% - the test yields the false-branch if the first token of the argument is
%%   whatsoever unexpandable token or a macro or an undefined control squence.
%% - the test yields the false-branch also if the argument is empty/contains
%%   no token at all.
%%=============================================================================
\newcommand\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive[1]{%
  \romannumeral
  \UD@CheckWhetherNull{#1}{\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
    \UD@CheckWhetherBrace{#1}{\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
      \UD@CheckWhetherLeadingExplicitSpace{#1}{\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
        \expandafter\expandafter\expandafter\UD@CheckWhetherMacro\UD@ExtractFirstArg{#1}%
        {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
        {%
          \expandafter\expandafter\expandafter\UD@CheckWhetherUndefined\UD@ExtractFirstArg{#1}%
          {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
          {%
            \expandafter\expandafter\expandafter\UD@CheckWhetherExpandable\UD@ExtractFirstArg{#1}%
            {\expandafter\UD@stopromannumeral\UD@firstoftwo}%
            {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
          }%
        }%
      }%
    }%
  }%
}%
\makeatother

\documentclass{article}

\parindent=0pt

\begin{document}

  1. Argment ``\verb*||''

\CheckWhetherArgumenHasFirstTokenWhichIsMacro{}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{a macro}.

  1. Argment ``\verb*| |''

\CheckWhetherArgumenHasFirstTokenWhichIsMacro{ }% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{a macro}.

  1. Argment ``\verb*|bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsMacro{bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{a macro}.

  1. Argment ``\verb*|\fi bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsMacro{\fi bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{a macro}.

  1. Argment ``\verb*|\UnDeFinEd\fi bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsMacro{\UnDeFinEd\fi bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{a macro}.

  1. Argment ``\verb*|\section \fi bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsMacro{\section \fi bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{a macro}.

\medskip\hrule\medskip

  1. Argment ``\verb*||''

\CheckWhetherArgumenHasFirstTokenWhichIsUndefined{}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an undefined control sequence}.

  1. Argment ``\verb*| |''

\CheckWhetherArgumenHasFirstTokenWhichIsUndefined{ }% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an undefined control sequence}.

  1. Argment ``\verb*|bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsUndefined{bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an undefined control sequence}.

  1. Argment ``\verb*|\fi bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsUndefined{\fi bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an undefined control sequence}.

  1. Argment ``\verb*|\UnDeFinEd\fi bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsUndefined{\UnDeFinEd\fi bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an undefined control sequence}.

  1. Argment ``\verb*|\section \fi bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsUndefined{\section \fi bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an undefined control sequence}.

\medskip\hrule\medskip

  1. Argment ``\verb*||''

\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an expandable primitive}.

  1. Argment ``\verb*| |''

\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{ }% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an expandable primitive}.

  1. Argment ``\verb*|bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an expandable primitive}.

  1. Argment ``\verb*|\fi bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{\fi bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an expandable primitive}.

  1. Argment ``\verb*|\UnDeFinEd\fi bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{\UnDeFinEd\fi bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an expandable primitive}.

  1. Argment ``\verb*|\section \fi bla \endcsname|''

\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{\section \fi bla \endcsname}% {\textbf{does}}{\textbf{does not}} have a first token which is \textbf{an expandable primitive}.

\vfill

Maybe you wish to crank out unexpandable primitives/unexpandable non-primitives also. Or refine to crank out global/long/protected macros also.\ When you find interesting methods for doing such things other than excessively parsing the \verb|\meaning| of the argument's first token, let me know. ;-)

These tests refer to the \verb|\meaning| of tokens.

Tests that refer to the "shape" of tokens can be non-trivial/unfeasible if they are to be implemented to work out by expansion-methods only and might need a lot of playing with delimited arguments.

\end{document}

enter image description here

Ulrich Diez
  • 28,770