分解参数列表中的参数

分解参数列表中的参数

当读取一个参数时,TeX 会做什么?

我想编写一个命令,该命令具有以逗号分隔的参数列表 ( \fun[a@b,c@d,e@f,g,])。每个参数应在分隔符处分成两部分@

以下示例显示没有额外包的当前代码(mypackage.sty):

\def\@explode#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}
\def\explode#1{\expandafter\@explode#1@@\@nil}

\newcommand{\fun}[1][a@b,c@d,e@f,g,]{
    \@for\elem:=#1\do{\explode{\elem}1:\@pOne-2:\@pTwo, }
}

现在调用带有或不带有参数的命令。

 \fun
 \fun[a@b,c@d,e@f,g,]

输出为:

1:a-2:b, 1:c-2:d, 1:e-2:f, 1:g-2:, 1:-2:,
1:a@b-2:, 1:c@d-2:, 1:e@f-2:, 1:g-2:, 1:-2:,

第一种情况下输出符合预期,但第二种情况下爆炸不起作用。

有什么区别?TeX 有什么用?我该如何调试/解决这个问题?

答案1

您可以使用另一个不同的分隔符,@而不会改变 catcodes,或者使用

\begingroup\lccode`;=`@ \lowercase{\endgroup
  \def\@explode#1;#2;#3\@nil}{\edef\@pOne{#1}\edef\@pTwo{#2}}

代替

\def\@explode#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}

但请注意,您将无法在\makeatletter部分代码中使用它。


完整游戏

\begingroup\lccode`;=`@ \lowercase{\endgroup
  \def\@explodeother#1;#2;#3\@nil}{\edef\@pOne{#1}\edef\@pTwo{#2}}
\def\@explodeletter#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}

\def\explode{\ifnum\catcode`@=11 \expandafter\explodeletter\else\expandafter\explodeother\fi}
\begingroup\lccode`;=`@ \lowercase{\endgroup
  \def\explodeother#1{\expandafter\@explodeother#1;;\@nil}}
\def\explodeletter#1{\expandafter\@explodeletter#1@@\@nil}

\newcommand{\fun}[1][a@b,c@d,e@f,g,]{%
  \@for\elem:=#1\do{\explode{\elem}1:\@pOne-2:\@pTwo, }%
}

最后一个检查 catcode@并相应地使用字母 @ 分区或其他 @ 分区。

答案2

这是 的分类代码问题@。定义中使用了分类代码“字母”,而 的分类代码@在主文档中通常是“其他”,例如:

\documentclass{article}

% \FunLetter using @ with category code "letter"

\makeatletter
\def\@explode@letter#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}
\newcommand*{\ExplodeLetter}[1]{\expandafter\@explode@letter#1@@\@nil}

\newcommand{\FunLetter}[1][a@b,c@d,e@f,g,]{%
    \@for\elem:=#1\do{\ExplodeLetter{\elem}1:\@pOne-2:\@pTwo, }%
}
\makeatother

% \FunOther using @ with category code "other"

\makeatletter
\begingroup
  \lccode`9=`@
\lowercase{%
  \endgroup
  \def\@explode@other#19#29#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}
  \newcommand*{\ExplodeOther}[1]{\expandafter\@explode@other#199\@nil}

  \newcommand{\FunOther}[1][a9b,c9d,e9f,g,]{%
      \@for\elem:=#1\do{\ExplodeOther{\elem}1:\@pOne-2:\@pTwo, }%
  }
}
\makeatother

\begin{document}
\setlength{\parindent}{0pt}

\verb|\makeatother|\makeatother\\
\begin{tabular}{@{}l@{ }l@{}}
\verb|\FunLetter|:& \FunLetter\\
\verb|\FunLetter[a@b,c@d,e@f,g,]|:& \FunLetter[a@b,c@d,e@f,g,]\\
\verb|\FunOther|:& \FunOther\\
\verb|\FunOther[a@b,c@d,e@f,g,]|:& \FunOther[a@b,c@d,e@f,g,]
\end{tabular}

\medskip
\verb|\makeatletter|\makeatletter\\
\begin{tabular}{@{}l@{ }l@{}}
\verb|\FunLetter|:& \FunLetter\\
\verb|\FunLetter[a@b,c@d,e@f,g,]|:& \FunLetter[a@b,c@d,e@f,g,]\\
\verb|\FunOther|:& \FunOther\\
\verb|\FunOther[a@b,c@d,e@f,g,]|:& \FunOther[a@b,c@d,e@f,g,]
\end{tabular}
\end{document}

结果

解决方法/解决方案:

  • 检查@类别代码为“字母”和“其他”的两个标记。
  • 使用不同的分隔符。例如,有很多键值解析器,如果等号=用作分隔符,例如 package kvsetkeys

答案3

正如已经解释过的,问题在于@定义时类别代码为 11,但使用时类别代码为 12。

这里有一个实现xparse;当参数没有指定要拆分的标记数时,\SplitArgument处理器会推送-NoValue-,因此有必要使用\IfValueT以便在存在时打印第二部分。

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\fun}{>{\SplitList{,}}O{a@b,c@d,e@f,g,}}{%
  \ProcessList{#1}{\explode}%
}

\NewDocumentCommand{\explode}{>{\SplitArgument{1}{@}}m}{%
  \doexplosion#1, %
}

\NewDocumentCommand{\doexplosion}{mm}{%
  1:#1-\IfValueT{#2}{#2}% No @ pushes -NoValue-
}

\begin{document}

\fun

\fun[a@b,c@d,e@f,g,]

\end{document}

在此处输入图片描述

答案4

这是关于实施对 letter@ 和其他-@ 进行检查的建议。

\documentclass{minimal}
\makeatletter
%%----------------------------------------------------------------------
%% Check whether argument is empty:
%%......................................................................
%% \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>}%
\newcommand\@CheckWhetherNull[1]{%
  \expandafter\@secondoftwo\string{\expandafter\@secondoftwo
  \expandafter{\expandafter{\string#1}\expandafter\@secondoftwo
  \string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@firstoftwo\expandafter\@secondoftwo\expandafter}\string
  }\@firstoftwo
}%
%%----------------------------------------------------------------------
%% Check whether arg contains no @ of catcode 11 (letter) on top brace
%% level
%%......................................................................
%% \CheckWhetherNoLetterAt{<Argument which is to be checked>}%
%%               {<Tokens to be delivered in case that argument which is
%%                to be checked does not contain @ of catcode 11>}%
%%               {<Tokens to be delivered in case that argument which is
%%                to be checked does contain @ of catcode 11>}%
\newcommand\@RemoveToLetterAt{}%
\long\def\@RemoveToLetterAt#1@{}%
\newcommand\@CheckWhetherNoLetterAt[1]{%
  \expandafter\@CheckWhetherNull
  \expandafter{\@RemoveToLetterAt#1@}%
}%
%%----------------------------------------------------------------------
%% \fun, \exolode etc:
%%......................................................................
\newcommand{\fun}[1][a@b,c@d,e@f,g,]{%%%
    \@for\elem:=#1\do{\explode{\elem}1:\@pOne-2:\@pTwo, }%%%
}%%
\begingroup
\lccode`\!=`\@% Catcode of ! is 12 (other). 
              % Lowercasing ! now yields @ of catcode 12 (other).
\lowercase{%
  \endgroup
  \newcommand\@AtLetterExplode{}%
  \def\@AtLetterExplode#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}%
  \newcommand\@AtOtherExplode{}%
  \def\@AtOtherExplode#1!#2!#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}%
  \newcommand\explode[1]{%
    \expandafter\@CheckWhetherNoLetterAt\expandafter{#1}%
    {\expandafter\@AtOtherExplode#1!!}%
    {\expandafter\@AtLetterExplode#1@@}%
    \@nil
  }%
}%

\makeatother


\begin{document}

\fun

\makeatletter

\fun[a@b,c@d,e@f,g,]

\makeatother

\fun[a@b,c@d,e@f,g,]

\end{document}

相关内容