有没有办法从宏中检索“源代码”和参数模式以供重用(即 \show 显示的内容)?

有没有办法从宏中检索“源代码”和参数模式以供重用(即 \show 显示的内容)?

我想连接两个具有相同参数签名的宏的“源代码”。如何获取(即展开一次)宏,而无需 TeX 搜索宏请求的参数?

\def\first#1#2thing{\one \two}
\def\second#1#2thing{\three \four}

% looking for: \def\union#1#2thing{\one \two \three \four}
\edef\union#1#2thing{\expandonce\first \expandonce\second}

如您所见,在这种情况下不使用参数,但签名必须保持不变。因此更好的做法是这样做:

\edef\union\argumentpatternof{\first}{\codeof\first\codeof\second}

[本节对该问题不具规范性... ;-]

我计划使用它来合并 TikZ 中的两种样式。比如\tikzset{every label/.append style with style={other style}}或者\tikzset{every label/.style={every label, other style}}或者\tikzset{every label/.ecode=\pgfkeysgetvalue{/tikz/every label/.@cmd}\pgfkeysgetvalue{/tikz/other style/.@cmd}}如果其中任何一个实际上可以工作...

答案1

可以恢复参数文本,只要不涉及类别代码的花哨更改。

这两个宏应该具有相同的非空参数文本。

\documentclass{article}
\usepackage{xparse}

\def\first#1#2thing{first(#1)(#2)}
\def\second#1#2thing{\mbox{second(#1)(#2)}}

\ExplSyntaxOn

\tl_new:N \l__siemer_para_tl
\tl_new:N \l__siemer_repl_first_tl
\tl_new:N \l__siemer_repl_second_tl
\exp_args_generate:n { NNNV }

\NewDocumentCommand{\makeunion}{mmm}
 {% #1 = new command name, #2 = first macro, #3 = second macro
  % #2 and #3 are assumed to have the same parameter text
  \tl_set_rescan:Nnx \l__siemer_para_tl { } { \cs_argument_spec:N #2 }
  \tl_set_rescan:Nnx \l__siemer_repl_first_tl { } { \cs_replacement_spec:N #2 }
  \tl_set_rescan:Nnx \l__siemer_repl_second_tl { } { \cs_replacement_spec:N #3 }
  \tl_put_right:NV \l__siemer_repl_first_tl \l__siemer_repl_second_tl
  \exp_args:NNNNV \exp_last_unbraced:NNo \cs_new:Npn #1 \l__siemer_para_tl \l__siemer_repl_first_tl
 }

\ExplSyntaxOff

\makeunion\union\first\second

\begin{document}

\first XYthing

\second XYthing

\union XYthing

\texttt{\meaning\union}

\end{document}

在此处输入图片描述

一些解释:\tl_set_rescan:Nnx我们用正确的类别代码存储替换文本;注意\cs_argument_spec:N只返回一个细绳(类别代码 12 的所有字符,除了保留类别代码 10 的空格)。替换文本也一样。

答案2

正如 David 在评论中指出的那样,TeX 无法找出参数签名,您必须提供它。但假设您可以,连接就很简单了。我引入了\concat4 个参数:

  1. 连接宏的名称,

  2. 要连接的两个宏的参数签名,

  3. 连接的第一个宏,

  4. 连接的第二个宏。

MWE。此处,\third\first和连接而成\second,两者都共享 的参数签名#1#2thing

\documentclass{article}
\usepackage[T1]{fontenc}
\def\first#1#2thing{one\{#1\}two\{#2\}}
\def\second#1#2thing{three\{#1\}four\{#2\}}

\def\concat#1#2#3#4{\def#1#2{#3#2#4#2}}
\begin{document}
\concat\third{#1#2thing}\first\second

\meaning\third

\first abthing

\second abthing

\third abthing

\third\today\textit{t}thing
\end{document}

在此处输入图片描述

答案3

只要⟨parameter text⟩您知道这些宏的共同点,您就可以执行以下操作:

\long\def\exchange#1#2{#2#1}%
\long\def\exchangeafterexpand#1#2{%
  \romannumeral0%
  \expandafter\exchange\expandafter{#1}{\exchange{ }{\expandafter}#2}%
}%

\def\first#1#2thing{first-one: #1 first-two: #2}
\def\second#1#2thing{second-one: #1 second-two: #2}

\expandafter\expandafter\expandafter\exchange
\expandafter\expandafter\expandafter{%
  \expandafter\expandafter\expandafter{%
      \exchangeafterexpand{\second{#1}{#2}thing}{\first{#1}{#2}thing}%
  }%
}{\def\union#1#2thing}%

\show\first

\show\second

\show\union

\bye

在此处输入图片描述

请注意,这可能不适用于根据 LaTeX 定义的宏\DeclareRobustCommand,也可能不适用于处理根据 LaTeX 定义的可选参数的宏。原因:此类宏实际上是一种机制,用户级命令在调用其名称源自用户级命令名称的内部命令之前\newcommand执行某些测试(测试\DeclareRobustCommand标记的定义;测试可选参数的存在)。\protect\newcommand

还要注意,这种使用参数语法{#1}/传递的参数进行扩展的方法{#2}只有在\firstand/or的定义文本\second本身不包含其他宏的定义(其中参数用##1,##2表示,即用哈希对表示)时才有效。这是因为在扩展过程中,哈希对将减少为单个哈希,这是我们不想要的。

因此,如果有 eTeX 扩展可用,我建议另一种方法:

\def\union#1#2thing{%
     Within the sequence 
        ⟨ Expansion of \first{⟨reserved token⟩1}{⟨reserved token⟩2}thing +
          Expansion of \second{⟨reserved token⟩1}{⟨reserved token⟩2}thing
        ⟩
     have every hash doubled and every instance of ⟨reserved token⟩ replaced by a single hash.
   }%
}%

这是我在下面的例子中实现的。这次我使用了 LaTeX。在下面的例子中,eTeX 扩展是实现可靠检查的必要条件,用于查明单个标记是否是类别代码 6(参数)的显式字符标记/用于查明单个标记是否是显式哈希字符标记。该测试的要点是:应用于\string哈希,您将获得类别代码 12(其他)的单个显式字符标记。将 eTeX\detokenize应用于哈希,您将获得两个这样的标记,因为\detokenize哈希值加倍。

下面的示例\romannumeral大量使用了 -expansion:-expansion 的要点\romannumeral是它\romannumeral本身会触发大量扩展工作,但如果在完成所有扩展工作后发现一个非正数,则不会默默地传递任何标记。此\romannumeral功能非常方便,因为它意味着在许多情况下,单个\expandafter-chain“命中”\romannumeral足以触发多个扩展步骤。您只需要确保扩展工作会产生一个标记序列,其前导标记是,例如0[space]。因为\romannumeral该序列将形成0非正数,因此该序列将被默默丢弃,而标记流中其后面的任何内容都将保留在原处。

我在回答这个问题时详细阐述了这一点“如何知道附加到 csname 宏时的 expandafter 数量?“。

\documentclass{article}

\makeatletter
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo,
%%    \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%%    \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%%    \UD@CheckWhetherLeadingSpace, \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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]{%
  \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}%
}%
%%-----------------------------------------------------------------------------
%% 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 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@CheckWhetherBrace[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}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#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}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no exclamation mark which is not nested 
%% in braces:
%%.............................................................................
%% \UD@CheckWhetherNoExclam{<Argument which is to be checked>}%
%%                         {<Tokens to be delivered in case that argument
%%                           contains no exclamation mark>}%
%%                         {<Tokens to be delivered in case that argument
%%                           contains exclamation mark>}%
%%
\newcommand\UD@GobbleToExclam{}\long\def\UD@GobbleToExclam#1!{}%
\newcommand\UD@CheckWhetherNoExclam[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}%
}%
%%-----------------------------------------------------------------------------
%%  \Parameterchar@reservedFork grabs the first thing behind a
%%  a token-sequence of pattern  !!\Parameterchar@reserved!
%%.............................................................................
\newcommand\Parameterchar@reservedFork{}
\long\def\Parameterchar@reservedFork#1!!\Parameterchar@reserved!#2#3!!!!{#2}%
%%-----------------------------------------------------------------------------
%% Check whether argument consists only of the token \Parameterchar@reserved
%%.............................................................................
\newcommand\UD@CheckWhetherParameterchar@reserved[1]{%
  \romannumeral0%
  \UD@CheckWhetherNoExclam{#1}{%
    \Parameterchar@reservedFork
    %Case #1 is empty/has no tokens:
      !#1!\Parameterchar@reserved!{\UD@Exchange{ }{\expandafter}\UD@secondoftwo}%
    %Case #1 = \Parameterchar@reserved:
      !!#1!{\UD@Exchange{ }{\expandafter}\UD@firstoftwo}%
    %Case #1 = something else without exclamation-mark:
      !!\Parameterchar@reserved!{\UD@Exchange{ }{\expandafter}\UD@secondoftwo}%
      !!!!%
  }{%
    %Case #1 = something else with exclamation-mark:
    \UD@Exchange{ }{\expandafter}\UD@secondoftwo
  }%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
%% \DoubleEveryHashAndReplaceParameterchar@reserved{<argument>}%
%%
%%   Each explicit catcode-6(parameter)-character-token of the <argument> 
%%   will be doubled. Each instance of \Parameterchar@reserved will be replaced
%%   by a single hash.
%%
%%   You obtain the result after two expansion-steps, i.e., 
%%   in expansion-contexts you get the result after "hitting" 
%%   \DoubleEveryHashAndReplaceParameterchar@reserved by two \expandafter.
%%   
%%   As a side-effect, the routine does replace matching pairs of explicit
%%   character tokens of catcode 1 and 2 by matching pairs of curly braces
%%   of catcode 1 and 2.
%%   I suppose this won't be a problem in most situations as usually the
%%   curly braces are the only characters of category code 1 / 2...
%%
%%   This routine needs \detokenize from the eTeX extensions.
%%-----------------------------------------------------------------------------
\newcommand\DoubleEveryHashAndReplaceParameterchar@reserved[1]{%
   \romannumeral0\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop{#1}{}%
}%
\newcommand\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop[2]{%
  \UD@CheckWhetherNull{#1}{ #2}{%
    \UD@CheckWhetherLeadingSpace{#1}{%
       \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
       \expandafter{\UD@removespace#1}{#2 }%
    }{%
      \UD@CheckWhetherBrace{#1}{%
        \expandafter\expandafter\expandafter\UD@PassFirstToSecond
        \expandafter\expandafter\expandafter{%
        \expandafter\UD@PassFirstToSecond\expandafter{%
            \romannumeral0%
            \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
            \romannumeral0%
            \UD@ExtractFirstArgLoop{#1\UD@SelDOm}{}%
        }{#2}}%
        {\expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
         \expandafter{\UD@firstoftwo{}#1}}%
      }{%
        \expandafter\UD@CheckWhetherHash
        \romannumeral0\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{#1}{#2}%
      }%
    }%
  }%
}%
\newcommand\UD@CheckWhetherHash[3]{%
  \expandafter\UD@CheckWhetherLeadingSpace\expandafter{\string#1}{%
    \expandafter\expandafter\expandafter\UD@CheckWhetherNull
    \expandafter\expandafter\expandafter{%
    \expandafter\UD@removespace\string#1}{%
      \expandafter\expandafter\expandafter\UD@CheckWhetherNull
      \expandafter\expandafter\expandafter{%
      \expandafter\UD@removespace\detokenize{#1}}{%
        % Something whose stringification yields a single space
        \UD@secondoftwo
      }{% Explicit space of catcode 6
        \UD@firstoftwo
      }%
    }{% Something whose stringification has a leading space
      \UD@secondoftwo
    }%
  }{%
    \expandafter\expandafter\expandafter\UD@CheckWhetherNull
    \expandafter\expandafter\expandafter{%
    \expandafter\UD@firstoftwo
    \expandafter{\expandafter}\string#1}{%
      \expandafter\expandafter\expandafter\UD@CheckWhetherNull
      \expandafter\expandafter\expandafter{%
      \expandafter\UD@firstoftwo
      \expandafter{\expandafter}\detokenize{#1}}{%
        % no hash
        \UD@secondoftwo
      }{% hash
        \UD@firstoftwo
      }%
    }{% no hash
      \UD@secondoftwo
    }%
  }%
  {% hash
    \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
    \expandafter{\UD@firstoftwo{}#2}{#3#1#1}%
  }{% no hash
    \UD@CheckWhetherParameterchar@reserved{#1}{%
      \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
      \expandafter{\UD@firstoftwo{}#2}{#3##}%
    }{%
      \expandafter\UD@DoubleEveryHashAndReplaceParameterchar@reservedLoop
      \expandafter{\UD@firstoftwo{}#2}{#3#1}%
    }%
  }%
}%
%%=============================================================================
\newcommand\UD@TripleExpandAndConcatSecondAndThird[3]{%
   \romannumeral0\expandafter\UD@PassFirstToSecond\expandafter{%
     \romannumeral0%
     \expandafter\UD@Exchange\expandafter{%
       \romannumeral0%
       \UD@Exchange{ }{\expandafter\expandafter\expandafter
                       \expandafter\expandafter\expandafter\expandafter}%
       #3%
     }{%
       \UD@Exchange{ }{\expandafter\expandafter\expandafter
                       \expandafter\expandafter\expandafter\expandafter}#2%
     }%
   }{ #1}%
}%
%%=============================================================================

\def\first#1#2thing{first-one: #1 first-two: #2 \def\firsttest##1{##1}}
\def\second#1#2thing{second-one: #1 second-two: #2 \def\secondtest##1{##1}}

\UD@TripleExpandAndConcatSecondAndThird{%
  \def\union#1#2thing%
}{%
  \expandafter\DoubleEveryHashAndReplaceParameterchar@reserved
  \expandafter{\first{\Parameterchar@reserved1}{\Parameterchar@reserved2}thing}%
}{%
  \expandafter\DoubleEveryHashAndReplaceParameterchar@reserved
  \expandafter{\second{\Parameterchar@reserved1}{\Parameterchar@reserved2}thing}%
}%

\show\first

\show\second

\show\union

\stop

在此处输入图片描述

当然,这只适用于⟨definition text⟩不包含标记的宏\Parameterchar@reserved,并且用类别代码 1(开始组)的显式左花括号字符标记替换类别代码 1(开始组)的任意显式字符标记并匹配类别代码 2(结束组)的任意显式字符标记与匹配类别代码 2(结束组)的显式右花括号字符标记无关紧要。

相关内容