如何定义仅进行参数替换的宏?

如何定义仅进行参数替换的宏?

当我定义一个内容包含环境的宏时,我遇到了一些问题lstlisting。例如,(假设!是环境中的转义字符lstlisting

\def\mycode#1{
    \begin{lstlisting}
    class Person {
        private int !{\bf #1}!;
        int getAge(){
            return !#1!;
        }
    }
    \end{lstlisting}
}

当我使用时\mycode{age}出现错误。有什么方法可以解决这个问题吗(请注意,我尝试过lrbox环境但没有成功)?

我真正想要的是一些宏定义的东西,它只是替换文本中的参数并将结果内容复制到调用宏的位置。也就是说,下面这行出现的位置

\mycode{age}

LaTeX 将取代它

   \begin{lstlisting}
    class Person {
        private int !{\bf age}!;
        int getAge(){
            return age;
        }
    }
    \end{lstlisting}

并开始处理\begin{lstlisting} ... \end{lstlisting}

这应该很简单,但不幸的是它似乎太复杂了(至少对我来说)。

答案1

环境lstlisting不能是另一个宏的参数。

\documentclass{article}
\usepackage[T1]{fontenc}    
\usepackage{listings}
\lstnewenvironment{mycode}[1]
  {\lstset{escapechar=!}\gdef\myPara{#1}}
  {}

\begin{document}

\begin{mycode}{age}
    class Person {
        private int !\textbf{\myPara}!;
        int getAge(){
            return !\myPara!;
        }
    }
\end{mycode}
\end{document}

在此处输入图片描述

答案2

使用支持的较新引擎\scantokens,您可以直接使用该引擎,而不需要使用外部文件。(*)

(*):它“相当于”使用外部文件。


方法 1:自定义宏\cprotDef,使定义此类宏变得简单

在下面的代码中,\cprotDef定义了,它与类似\cprotect,但是用于宏定义。

它似乎是“只进行参数替换”,同时使用户端宏定义尽可能简单(尽管在这种情况下输入的 catcode 信息被丢弃)

请注意,使用此命令定义的命令将使用执行点的 catcode,而不是定义点(例如\ExplSyntaxOff在调用宏本身之前)。

%! TEX program = lualatex
\documentclass{article}
\usepackage{listings}
\begin{document}


% ======== Define the helper command \cprotDef to make the main macro definition (see below) easy. ========

\errorcontextlines=10000
\ExplSyntaxOn

% format: \cprotDef \cs_set_protected:Npn \control_sequence (parameter text) {(replacement text)}
% or \cprotDef {\cs_set_protected:Npn \control_sequence (parameter text)} {(replacement text)}
\cs_set_protected:Npn \cprotDef #1 # {
    \tl_if_empty:nTF {#1} {
        \cprotDef_auxi:n
    } {
        \cprotDef_auxi:n {#1}
    }
}

\cs_set_protected:Npn \cprotDef_auxi:n #1 {
    \begingroup

    % after reading the control sequence and the parameter text, set catcode

    % (list of all special characters can be found in \dospecials macro)

    % special characters that keeps their meaning
    %\{
    %\}
    %\#

    % extra not listed in dospecials
    \char_set_catcode_other:N \^^I
    \char_set_catcode_other:N \^^J

    % the rest are set to (other).
    \char_set_catcode_other:N \  %space
    \char_set_catcode_other:N \\
    \char_set_catcode_other:N \$
    \char_set_catcode_other:N \&
    \char_set_catcode_other:N \^
    \char_set_catcode_other:N \_
    \char_set_catcode_other:N \%
    \char_set_catcode_other:N \~

    \newlinechar=`\^^J % which character is considered a newline when printed / by \scantokens
    \endlinechar=`\^^J % which character is put after each line
    \relax

    % then read in the {(replacement text)}
    \cprotDef_aux:nn {#1}
}

\cs_set_protected:Npn \cprotDef_aux:nn #1 #2 {
    % #1 = (stored)  approximately `\def \control_sequence (parameter_text)'
    % #2 = (newly read)  (replacement_text), with catcode of most special characters set to (other)

    \endgroup  % restore the catcode values

    #1 {\scantokens{#2}}
}
\ExplSyntaxOff

% ======== Define the main command. ========

\cprotDef \protected \def \mycode #1 {
\begin{lstlisting}[escapechar=!]
class person {
    private int !{\bf #1}!;
    int getAge(){
        return #1;
    }
}
\end{lstlisting}
}

% ======== Use that command. ========
\mycode{age}


\end{document}

方法2.使用scontents

在此特别的您实际上并不需要将参数的值代入代码本身(可以在外部分配一个宏并在内部使用它)。

%! TEX program = xelatex
\documentclass{article}
\usepackage{listings}
\usepackage{scontents}
\begin{document}

\begin{scontents}[store-env=myStorageListing]
\begin{lstlisting}[escapechar=!]
class person {
    private int !{\bf \myAge}!;
    int getAge(){
        return age;
    }
}
\end{lstlisting}
\end{scontents}

\ExplSyntaxOn
% ======== Define the main command. ========
\NewDocumentCommand \myCommand {m} {
    \tl_set:Nn \myAge {#1}
    \getstored{myStorageListing}
}
\ExplSyntaxOff

% ======== Use that command. ========
\myCommand {age}

\end{document}

方法 3:生吃\scantokens

一次构建一部分代码,最后\scantokens构建完成。这是最强大的方法,但可能有点难以阅读。

更多解释:https://tex.stackexchange.com/a/622505/250119

%! TEX program = xelatex
\documentclass{article}
\usepackage{listings}
\begin{document}
\ExplSyntaxOn
% ======== Define \myTLSetVerbatim : similar to \tl_set:Nn, but second argument is of xparse's +v type. ========
\NewDocumentCommand \myTLSetVerbatim {m +v} {
    \tl_set:Nn #1 {#2}
}
\ExplSyntaxOff
% ======== Define some auxiliary helper TLs. ========
\myTLSetVerbatim \myTextBefore @\begin{lstlisting}[escapechar=!]
class person {
    private int !{\bf @
\myTLSetVerbatim \myTextAfter   @}!;
    int getAge(){
        return age;
    }
}
\end{lstlisting}@
% ======== Define the main command. ========
\ExplSyntaxOn
\NewDocumentCommand \myCommand {m} {
    \exp_args:Nx \scantokens {
        \myTextBefore #1 \myTextAfter
    }
}
\ExplSyntaxOff
% ======== Use that command. ========
\myCommand {age}
\end{document}

因为您“仅替换文本”,所以即使有参数,您也不能将逐字内容放入其中,+v除非您自己逐字放入(!{\bf <verbatim content>}!在环境中也不起作用)。

方法4.\cprotect

\cprotect可以像这样使用:(内部使用外部文件)

该解决方案不太简洁,scontents并且具有相同的限制。

%! TEX program = xelatex
\documentclass{article}
\usepackage{listings}
\usepackage{cprotect}
\begin{document}

% ======== Define the helper commands.
\cprotect {\newcommand \myCommandAux} {
\begin{lstlisting}[escapechar=!]
class person {
    private int !{\bf \myAge}!;
    int getAge(){
        return age;
    }
}
\end{lstlisting}
}
\ExplSyntaxOn
% ======== Define the main command. ========
\NewDocumentCommand \myCommand {m} {
    \tl_set:Nn \myAge {#1}
    \myCommandAux
}
\ExplSyntaxOff

% ======== Use that command. ========
\myCommand {age}

\end{document}

答案3

该环境的问题lstlisting与以下环境的问题相同verbatim

它期望其“主体”和短语\end{lstlisting}在某些非标准 catcode 制度下进行标记化,从而产生一组与在标准 catcode 制度下进行标记化所获得的标记集不同的标记。

如果来自宏的替换文本,而\mycode该宏的定义文本在标准 catcode 制度下被标记,那么该短语\end{lstlisting}将不会被识别为环境的结束lstlisting

作为一种解决方法,我可以提供例程

\DefineVerbatimToScantokens{⟨control-word-token⟩}{⟨xparse-argument-specifiers⟩}{%
  ⟨verbatim-material to be passed to \scantokens⟩
}%

⟨逐字逐句地传递给 \scantokens⟩在 verbatim-catcode-régime 下读取并标记。

然后在⟨逐字逐句地传递给 \scantokens⟩每一个都#被 catcode 6(参数)的字符标记替换。

然后⟨控制字标记⟩定义为根据以下方式处理参数⟨xparse-参数说明符⟩并通过⟨逐字逐句地传递给 \scantokens⟩\scantokens

\documentclass{article}
\usepackage[T1]{fontenc}    
\usepackage{listings}

%=== Code of \DefineVerbatimToScantokens ========================
% With older LaTeX-releases uncomment the following line:
%\usepackage{xparse}   
\NewDocumentCommand\DefineVerbatimToScantokens{mm}{%
  \begingroup
  \catcode`\^^I=12\relax
  \InnerDefineVerbatimToScantokens{#1}{#2}%
}%
\begingroup
\makeatletter
\def\InnerDefineVerbatimToScantokens#1#2{%
  \endgroup
  \NewDocumentCommand\InnerDefineVerbatimToScantokens{mm+v}{%
    \endgroup\ReplaceHashloop{##3}{##1}{##2}%
  }%
  \newcommand\ReplaceHashloop[3]{%
    \ifcat$\detokenize\expandafter{\Hashcheck##1#1}$%
    \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {%
      \NewDocumentCommand{##2}{##3}{%
         \begingroup\newlinechar=\endlinechar
         \scantokens{\endgroup##1#2}%
      }%
    }{%
      \expandafter\ReplaceHashloop\expandafter{\Hashreplace##1}{##2}{##3}%
    }%
  }%
  \@ifdefinable\Hashcheck{\long\def\Hashcheck##1#1{}}%
  \@ifdefinable\Hashreplace{\long\def\Hashreplace##1#1{##1####}}%
}%
\catcode`\%=12\relax
\catcode`\#=12\relax
\InnerDefineVerbatimToScantokens{#}{%}%
%=== End of code of \DefineVerbatimToScantokens =================


% Be aware that indenting does matter within \DefineVerbatimToScantokens's
% <verbatim-material to be passed to \scantokens>-argument:

\DefineVerbatimToScantokens\mycode{m}{%
\begin{lstlisting}[escapechar=!]
class Person {
    private int !\textbf{#1}!;
    int getAge(){
        return !#1!;
    }
}
\end{lstlisting}
}%



\begin{document}

\noindent aaa

\noindent \mycode{age}

\end{document}

在此处输入图片描述

相关内容