自动替换未定义的控制序列

自动替换未定义的控制序列

我想知道是否可以创建一个宏,这样当我输入未定义的控制序列(如\Var\PDAG等)时,它会自动将它们转换为数学运算符。

我要求它避免创建大量的\newcommands。

提前非常感谢您。

答案1

这是一个无包装模板,您可以根据自己的需要进行调整。

\documentclass{article}
\usepackage{amsmath}

\makeatletter
\expandafter\def\expandafter\MakeMyDay\@backslashchar
    #1@#2{% #1 = name without backslash
          % #2 = control sequence
% YOU CAN CUSTOMIZE HERE WHATEVER YOU WANT
    \newcommand{#2}{\operatorname{#1}}%
}

% lovely \expandafter's
\@tfor\x:=\Var\PDFAg\Egreg\Mond\Moon\Sun
  \do{\expandafter\expandafter\expandafter
      \expandafter\expandafter\expandafter
      \expandafter
      \MakeMyDay
      \expandafter\expandafter\expandafter
      \string\expandafter\x\expandafter @\x}%


\makeatother
\begin{document}
$\Var$, $\PDFAg$, $\Egreg$, $\Mond$, $\Moon$, $\Sun$

\end{document}

输出:

https://i.stack.imgur.com/bxdYb.png

答案2

仅代表我个人观点:

\documentclass{article}
\usepackage{amsmath}

\makeatletter
% ---------------------------------------------------------------
% \name foo{bar} -> foo\bar
% \name {bar} -> \bar
\newcommand\name{}%
\long\def\name#1#{\UD@innername{#1}}%
\newcommand\UD@innername[2]{%
  \expandafter\UD@exchange\expandafter{\csname#2\endcsname}{#1}%
}%
%
\newcommand\UD@exchange[2]{#2#1}%
% ---------------------------------------------------------------
\newcommand\DefineOneNiceMathOperatorFromString[1]{%
  \name\newcommand{#1}{\ensuremath{\operatorname{#1}}}%
}%
% ---------------------------------------------------------------
\newcommand\DefineSeveralNiceMathOperatorsFromStrings[1]{%
  \@tfor\x:=#1\do{%
    \expandafter\DefineOneNiceMathOperatorFromString
    \expandafter{\x}%
  }%
}%
% ---------------------------------------------------------------
\newcommand\DefineOneNiceMathOperatorFromCsToken[1]{%
  \begingroup\escapechar=-1\relax
  \expandafter\endgroup
  \expandafter\DefineOneNiceMathOperatorFromString\expandafter{\string#1}%
}%
% ---------------------------------------------------------------
\newcommand\DefineSeveralNiceMathOperatorsFromCsTokens[1]{%
  \@tfor\x:=#1\do{%
    \expandafter\DefineOneNiceMathOperatorFromCsToken
    \expandafter{\x}%
  }%
}%
% ---------------------------------------------------------------
\makeatother

\DefineOneNiceMathOperatorFromString{Alice}

\DefineSeveralNiceMathOperatorsFromStrings{{Bob}{Carol}{Dave}{Ted}{Eve}}

\DefineOneNiceMathOperatorFromCsToken{\Mallory}

\DefineSeveralNiceMathOperatorsFromCsTokens{\Oscar\Peggy\Victor\Trudy\Trend}

\begin{document}

\verb|$\Alice$| yields: $\Alice$

\verb|$\Bob$| yields: $\Bob$

\verb|$\Carol$| yields: $\Carol$

\verb|$\Dave$| yields: $\Dave$

\verb|$\Ted$| yields: $\Ted$

\verb|$\Eve$| yields: $\Eve$

\verb|$\Mallory$| yields: $\Mallory$

\verb|$\Oscar$| yields: $\Oscar$

\verb|$\Peggy$| yields: $\Peggy$

\verb|$\Victor$| yields: $\Victor$

\verb|$\Trudy$| yields: $\Trudy$

\verb|$\Trend$| yields: $\Trend$

\hrulefill

\verb|$\name{Alice}$| yields: $\name{Alice}$

\verb|$\name{Bob}$| yields: $\name{Bob}$

\verb|$\name{Carol}$| yields: $\name{Carol}$

\verb|$\name{Dave}$| yields: $\name{Dave}$

\verb|$\name{Ted}$| yields: $\name{Ted}$

\verb|$\name{Eve}$| yields: $\name{Eve}$

\verb|$\name{Mallory}$| yields: $\name{Mallory}$

\verb|$\name{Oscar}$| yields: $\name{Oscar}$

\verb|$\name{Peggy}$| yields: $\name{Peggy}$

\verb|$\name{Victor}$| yields: $\name{Victor}$

\verb|$\name{Trudy}$| yields: $\name{Trudy}$

\verb|$\name{Trend}$| yields: $\name{Trend}$

\end{document}

在此处输入图片描述

答案3

你不能这样做,但如果你想要一种以简单的方式定义几个运算符的简单方法,那么它就在这里:

\makeatletter
\newcommand{\DeclareSeveralMathOperators}[1]{%
  \@for\next:=#1\do{%
    \begingroup\edef\x{\endgroup\p@DeclareMathOperator{\next}{\next}}\x
  }%
}
\protected\def\p@DeclareMathOperator#1{%
  \expandafter\DeclareMathOperator\csname #1\endcsname
}
\makeatother

完整示例(操作符如 jfbu 的示例):

\documentclass{article}
\usepackage{amsmath}

\makeatletter
\newcommand{\DeclareSeveralMathOperators}[1]{%
  \@for\next:=#1\do{%
    \begingroup\edef\x{\endgroup\p@DeclareMathOperator{\next}{\next}}\x
  }%
}
\protected\def\p@DeclareMathOperator#1{%
  \expandafter\DeclareMathOperator\csname #1\endcsname
}
\makeatother

\DeclareSeveralMathOperators{Var,PDFAg,Egreg,Mond,Moon,Sun}

\begin{document}

$\Var$, $\PDFAg$, $\Egreg$, $\Mond$, $\Moon$, $\Sun$

\end{document}

如果您使用expl3;会简单得多,\exp_args:Nc我们将转换为具有名称的{##1}控制序列,然后才能开始执行。像往常一样,代表我们提供给的列表中的当前项目:##1\DeclareMathOperator##1\DeclareSeveralMathOperators

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\DeclareSeveralMathOperators}{m}
 {
  \clist_map_inline:nn { #1 }
   {
    \exp_args:Nc \DeclareMathOperator { ##1 } { ##1 }
   }
 }
\ExplSyntaxOff

\DeclareSeveralMathOperators{Var,PDFAg,Egreg,Mond,Moon,Sun}

\begin{document}

$\Var$, $\PDFAg$, $\Egreg$, $\Mond$, $\Moon$, $\Sun$

\end{document}

在此处输入图片描述

答案4

从哲学上讲/作为一个有争议的问题,人们可能会提出这样的问题:您是否还需要测试来查明
- 给定的控制序列标记是否已经定义。
- 给定的控制序列名称表示已经定义的控制序列标记。

如果有 eTeX 扩展可用,则可以将原语用于\ifdefined进一步的和\ifcsname后者。

如果它们不可用,则\@ifundefined可以像在旧版本的 LaTeX2e 内核中一样实现一个宏,它从\csname..\endcsname构造中形成控制序列标记,然后检查该标记的含义是否等于 -primitive 的含义\relax
\csname..\endcsname在当前范围内(即使\globaldefs是正的,也只在当前范围内这样做)在未定义的情况下为所讨论的控制序列标记分配 -primitive 的含义\relax。)

\newcommand\@ifundefined[1]{%
  \expandafter\ifx\csname#1\endcsname\relax
    \expandafter\@firstoftwo 
  \else
    \expandafter\@secondoftwo
  \fi 
}

\relax使用旧版 LaTeX2e 内核方法,未定义的控制序列将在首次测试后在当前范围内定义。使用旧版 LaTeX2e 内核方法,无法区分在\relax测试之前已定义控制序列的情况和在测试之前根本没有定义控制序列的情况。

通过引入一些分组/作用域技巧,既可以避免在当前范围内将 -primitive 的含义永久地分配\relax给未定义的控制序列,又可以区分上述情况:

\documentclass{article}

\begingroup
\makeatletter
\@firstofone{%
  \endgroup
  \newcommand\old@ifundefined[1]{%
    \expandafter\ifx\csname #1\endcsname\relax
      \expandafter\@firstoftwo 
    \else
      \expandafter\@secondoftwo
    \fi 
  }%
  \newcommand\CheckWhetherUndefined[1]{%
    \begingroup
    \expandafter\expandafter\expandafter\expandafter
    \expandafter\expandafter\expandafter\endgroup
    \old@ifundefined{#1}%
    {% -> defined equal to \relax or undefined
      \begingroup
      \expandafter\expandafter\expandafter\endgroup
      \old@ifundefined{#1}{%
        % -> defined to equal \relax
        \@secondoftwo
      }{%
        % -> undefined
        \@firstoftwo
      }%
    }{% -> defined
      \@secondoftwo
    }%
  }%
}%

\begin{document}

\makeatletter
\ttfamily\selectfont

\def\foo{defined}
\let\bar=\relax

\verb|\meaning\foo| $\to$ \meaning\foo

\verb|\meaning\bar| $\to$ \meaning\bar

\verb|\meaning\UnDeFINed| $\to$ \meaning\UnDeFINed

Test with \string\CheckWhetherUndefined:

\verb|\CheckWhetherUndefined{foo}{undefined}{defined}| $\to$
\CheckWhetherUndefined{foo}{undefined}{defined}

\verb|\CheckWhetherUndefined{bar}{undefined}{defined}| $\to$
\CheckWhetherUndefined{bar}{undefined}{defined}

\verb|\CheckWhetherUndefined{UnDeFINed}{undefined}{defined}| $\to$
\CheckWhetherUndefined{UnDeFINed}{undefined}{defined}

Test with \LaTeXe's \string\old@ifundefined:

\verb|\old@ifundefined{foo}{undefined}{defined}| $\to$
\old@ifundefined{foo}{undefined}{defined}

\verb|\old@ifundefined{bar}{undefined}{defined}| $\to$
\old@ifundefined{bar}{undefined}{defined}

\verb|\old@ifundefined{UnDeFINed}{undefined}{defined}| $\to$
\old@ifundefined{UnDeFINed}{undefined}{defined}

\end{document}

在此处输入图片描述

但仍然存在一些问题:

如果您希望使用控制序列标记而不是给定名称来执行\old@ifundefined/路由,则需要从控制序列标记中派生名称。您可以通过使用并删除前导转义字符(如果存在)来实现这一点。为了确定是否存在前导转义字符,如果存在,则确定该字符是类别代码 12(其他)的字符标记还是空格标记,您可以评估整数参数的当前值。或者,可以通过(在本地范围内)分配负值来确保不存在前导转义字符。\CheckWhetherUndefined\string\escapechar\escapechar

但这并没有考虑到有人可能想在活动角色标记上应用测试。

除此之外,在扩展上下文中,需要对本地范围进行分组/引入以及为整数参数分配值的测试\escapechar是行不通的,因为在扩展发生时,分组/范围和分配不会同时发生,因为扩展和分组/范围/分配发生在 (La)TeX 消化装置的不同部分。

还需要记住的是,实现任何测试通常都是以宏的形式进行的,而宏确实是以参数方式处理事物,因此不一定是以标记方式处理事物。

如何处理将多个标记作为参数传递的情况?
如何处理参数的第一个标记是 catcode 1 的左括号的情况?
如何处理将(单个)非控制序列标记作为参数传递的情况?
如何处理参数为空的情况?
如何处理测试不应该针对控制序列标记而是针对活动字符标记的情况?

也许,没有 eTeX 扩展的完全可扩展测试会很好地查明参数的第一个标记是否未定义。

我在下面的例子中实现了这样的测试。

测试的要点是:

首先附加一个点字符,因此附加的不是未定义的内容,以确保参数不为空。
然后,可扩展地检查参数的第一个标记是否是左括号/显式 catcode-1 字符标记。如果是,则显然该参数的第一个标记不是未定义的。
否则,可以通过 来“命中”参数的第一个标记\meaning,在获得该“命中”结果后,可以检查参数现在是否具有由类别代码 12(其他)的字符组成的前导短语“未定义”:

\documentclass{article}
\makeatletter
%%----------------------------------------------------------------------
%% Paraphernalia:
%%----------------------------------------------------------------------
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
%%----------------------------------------------------------------------
%% Expandably within two expansion steps 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>
%%
%% A concern in his posting is that the argument is hit with \string
%% after some expansions which in edge cases might result in unbalancing
%% surrounding \if..\fi-constructs if the macro is used inside of such
%% \if..\fi-constructs.
%%
%% That challenging concern sickened me. ;-)
%%
%% Therefore I decided to implerment a variant where this cannot happen
%% as expansion is forced by \romannumeral:
%%
%% After the first expansion-step, \string is not applied yet.
%% After the second expansion-step, any possibly disturbing remainders
%% are already removed due to \romannumeral-expansion.
%%
%% No eTeX- or whatsoever extensions. No \if.. .Only \romannumeral,
%% digit 0, space token for terminating \romannumeral-expansion,
%% \string, \expandafter, \UD@firstoftwo, \UD@secondoftwo, {, }.
%%
%% May 20, 2016
%%
%% Ulrich Diez (e-mail: [email protected])
%%......................................................................
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%----------------------------------------------------------------------
%% Expandably within two expansion steps check whether argument's first 
%% token is a catcode-1-character
%%----------------------------------------------------------------------
%% \UD@CheckWhetherLeadingBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
%%......................................................................
\newcommand\UD@CheckWhetherLeadingBrace[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%----------------------------------------------------------------------
%% Expandably within two expansion steps check whether macro argument 
%% has an undefined token as its first token:
%%----------------------------------------------------------------------
%% \CheckWhetherArgumentHasAFirstTokenThatIsUndefined%
%%                      {<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked does have an
%%                        undefined token as its first token.>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked does not have an
%%                        undefined token as its first token.>}%
%%......................................................................
\begingroup
\newcommand\CheckWhetherArgumentHasAFirstTokenThatIsUndefined[1]{%
  \endgroup
  \newcommand\CheckWhetherArgumentHasAFirstTokenThatIsUndefined[1]{%
    \romannumeral0\UD@CheckWhetherLeadingBrace{##1}%
    {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
    {\expandafter\UD@secondoftwo\string{\expandafter
     \CheckWhetherArgumentHasAFirstTokenThatIsUndefinedB
     \meaning##1.#1}{}}%
  }%
  \newcommand\CheckWhetherArgumentHasAFirstTokenThatIsUndefinedB{}%
  \long\def\CheckWhetherArgumentHasAFirstTokenThatIsUndefinedB##1#1{%
    \UD@CheckWhetherNull{##1}%
    {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
    {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
     \expandafter\expandafter\expandafter}\expandafter\expandafter
     \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
  }%
}%
\begingroup
\edef\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{%
  \endgroup
  \noexpand\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{%
    \string u\string n\string d\string e\string f%
    \string i\string n\string e\string d%
  }%
}%
\CheckWhetherArgumentHasAFirstTokenThatIsUndefined
%%----------------------------------------------------------------------
%% A macro for those who prefer nesting \if-conditionals:
%%   (Be aware that after reading the closing brace behind
%%    <Argument which is to be checked> TeX' reading-apparatus will be 
%%    in state M (middle of line) and therefore you need to use
%%    comment-characters for avoiding unwanted spaces!!!)
%%----------------------------------------------------------------------
%%   \if\ArgumentHasAFirstTokenThatIsUndefined{<Argument which is to be checked>}%
%%     <Tokens to be delivered in case that argument
%%      which is to be checked does have an
%%      undefined token as its first token.>
%%  \else
%%     <Tokens to be delivered in case that argument
%%      which is to be checked does not have an
%%      undefined token as its first token.>
%%  \fi
%%......................................................................
\newcommand\ArgumentHasAFirstTokenThatIsUndefined[1]{%
  0\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{#1}{0}{1}%
}%
\makeatother

\begin{document}
\parindent=0ex
\parskip=\baselineskip
\footnotesize
\nonfrenchspacing
\ttfamily\selectfont

Testing \string\CheckWhetherArgumentHasAFirstTokenThatIsUndefined

\verb|\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{a}{true}{false}| $\to$\\
\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{a}{true}{false}

\verb|\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{\TeX}{true}{false}| $\to$\\
\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{\TeX}{true}{false}

\verb|\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{\uNdEfInED}{true}{false}| $\to$\\
\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{\uNdEfInED}{true}{false}

\verb|\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{a\biZarre\fi}{true}{false}| $\to$\\
\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{a\biZarre\fi}{true}{false}

\verb|\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{\TeX\biZarre\fi}{true}{false}| $\to$\\
\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{\TeX\biZarre\fi}{true}{false}

\verb|\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{\uNdEfInED\biZarre\fi}{true}{false}| $\to$\\
\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{\uNdEfInED\biZarre\fi}{true}{false}

\verb|\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{{in braces}}{true}{false}| $\to$\\
\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{{in braces}}{true}{false}

\verb|\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{}{true}{false}| $\to$\\
\CheckWhetherArgumentHasAFirstTokenThatIsUndefined{}{true}{false}

Testing \string\if\string\ArgumentHasAFirstTokenThatIsUndefined

\verb|\if\ArgumentHasAFirstTokenThatIsUndefined{a}true\else false\fi| $\to$\\
\if\ArgumentHasAFirstTokenThatIsUndefined{a}true\else false\fi

\verb|\if\ArgumentHasAFirstTokenThatIsUndefined{\TeX}true\else false\fi| $\to$\\
\if\ArgumentHasAFirstTokenThatIsUndefined{\TeX}true\else false\fi

\verb|\if\ArgumentHasAFirstTokenThatIsUndefined{\uNdEfInED}true\else false\fi| $\to$\\
\if\ArgumentHasAFirstTokenThatIsUndefined{\uNdEfInED}true\else false\fi

\verb|\if\ArgumentHasAFirstTokenThatIsUndefined{a\biZarre\fi}true\else false\fi| $\to$\\
\if\ArgumentHasAFirstTokenThatIsUndefined{a\biZarre\fi}true\else false\fi

\verb|\if\ArgumentHasAFirstTokenThatIsUndefined{\TeX\biZarre\fi}true\else false\fi| $\to$\\
\if\ArgumentHasAFirstTokenThatIsUndefined{\TeX\biZarre\fi}true\else false\fi

\verb|\if\ArgumentHasAFirstTokenThatIsUndefined{\uNdEfInED\biZarre\fi}true\else false\fi| $\to$\\
\if\ArgumentHasAFirstTokenThatIsUndefined{\uNdEfInED\biZarre\fi}true\else false\fi

\verb|\if\ArgumentHasAFirstTokenThatIsUndefined{{in braces}}true\else false\fi| $\to$\\
\if\ArgumentHasAFirstTokenThatIsUndefined{{in braces}}true\else false\fi

\verb|\if\ArgumentHasAFirstTokenThatIsUndefined{}true\else false\fi| $\to$\\
\if\ArgumentHasAFirstTokenThatIsUndefined{}true\else false\fi

\end{document}

在此处输入图片描述

相关内容