我尝试制作一个宏来检查某个宏是否已定义,如果已定义,它将打印该宏的定义。如果宏未定义,它将显示这一点。我对它的工作原理感到满意,但我想做一些改进。以下是该宏的 MWE:
\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}
输出:
我想修复 3 件事:
1.
不幸的是,如果我\defcheck{\usepackage}
这样做,它会返回一个错误,说LaTeX Error: Can be used only in preamble
。我怎样才能避免里面的任何错误\defcheck
?有没有办法不执行作为参数给出的任何命令\defcheck
?我在想也许expl3
语法可以解决这个问题?或者也许只是提供文本,然后以某种方式在需要时将其变成命令?(例如,\defcheck{usepackage}
然后以某种方式插入\
之前#1
以使其成为命令)
2.
当定义宏时,命令应在冒号前打印宏,如下所示:
\defcheck : --> \long macro:#1-> \ifdefined (...)
。这可以通过\textbacklash
上面提到的解决方案(\defcheck{usepackage}
而不是\defcheck{\usepackage}
)来修复。对于未定义的宏,我希望它类似于:! : --> Undefined macro; "\undefined"
3.
我想将所有\defcheck
s 都“收集”到文档中的同一位置(在某一节或章节下),如下所示:
最好将其放在 PDF 的末尾(在其自己的页面上),而不是出现在目录中。所有\defcheck
s 都应按照其调用顺序自动归入此部分。
边注:如何在同一节下打印其他命令?例如,如果我要发出类似的命令来检查颜色是否已定义,我如何在同一节中打印结果(但可能有自己的子节)?
编辑:当我尝试\hbox
打印: --> \hbox
时。为什么无法显示定义\hbox
?当我尝试时也发生了类似的事情\csname
。
编辑2:我现在明白了,我可能在第 3 点没有表达清楚。如果我\defcheck
在第一页的段落中间使用,我仍然希望它出现在文档的最后一页上。最好是,第一次使用宏会自动在文档末尾创建一个新页面,所有其他defcheck
命令也都放在这个新页面中。
答案1
如果删除虚假的,则不会有错误\defcheck
。
但是,您获得的输出并不是很有帮助。
\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}
你能得到吗任何这些信息来自哪里?
使用技巧
\expandafter\defcheck\csname makebox \endcsname
你会得到真实的的定义\makebox
,但它是
而且它也没有提供什么实际信息。
为了积累\defcheck
命令并在最后打印它们,您可以执行以下操作。
\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}
我仍不确定这种东西是否有用。
答案2
除了 egreg 已经写的内容之外,您可能对一些测试感兴趣,这些测试侧重于可以通过解析宏参数的第一个标记的含义推断出的属性:
\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}.
2. Argment ``\verb*| |''
\CheckWhetherArgumenHasFirstTokenWhichIsMacro{ }%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{a macro}.
3. Argment ``\verb*|bla \endcsname|''
\CheckWhetherArgumenHasFirstTokenWhichIsMacro{bla \endcsname}%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{a macro}.
4. Argment ``\verb*|\fi bla \endcsname|''
\CheckWhetherArgumenHasFirstTokenWhichIsMacro{\fi bla \endcsname}%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{a macro}.
5. 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}.
6. 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
7. Argment ``\verb*||''
\CheckWhetherArgumenHasFirstTokenWhichIsUndefined{}%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{an undefined control sequence}.
8. Argment ``\verb*| |''
\CheckWhetherArgumenHasFirstTokenWhichIsUndefined{ }%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{an undefined control sequence}.
9. Argment ``\verb*|bla \endcsname|''
\CheckWhetherArgumenHasFirstTokenWhichIsUndefined{bla \endcsname}%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{an undefined control sequence}.
10. 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}.
11. 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}.
12. 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
13. Argment ``\verb*||''
\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{}%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{an expandable primitive}.
14. Argment ``\verb*| |''
\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{ }%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{an expandable primitive}.
15. Argment ``\verb*|bla \endcsname|''
\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{bla \endcsname}%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{an expandable primitive}.
16. Argment ``\verb*|\fi bla \endcsname|''
\CheckWhetherArgumenHasFirstTokenWhichIsExpandablePrimitive{\fi bla \endcsname}%
{\textbf{does}}{\textbf{does not}} have a first token which is
\textbf{an expandable primitive}.
17. 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}.
18. 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}