当读取一个参数时,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}
解决方法/解决方案:
- 检查
@
类别代码为“字母”和“其他”的两个标记。 - 使用不同的分隔符。例如,有很多键值解析器,如果等号
=
用作分隔符,例如 packagekvsetkeys
。
答案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}