我尝试构建一个名为的宏mlist
,它有两种用途:
\mlist{...} will redefine the content of \clist
\mlist+{...} will apppend something to \clist
我尝试以下代码失败了。我的代码有什么问题?
代码:
\documentclass{article}
\makeatletter
\long\def\mlist#1#2{
\if#1+\relax \edef\clist{\clist,#2}%append #2 to \clist
\else
\ifx\@nil#2
\def\clist{#1}#2 %redefine \clist
\else
\mlist#1\@nil
\fi
\fi
}
\makeatother
\begin{document}
\def\clist{}
\mlist{cc}\clist\par% "cc" expected
\mlist+{dd}\clist\par % "cc,dd" expected
\end{document}
答案1
您的\mlist
宏有两个参数\mlist{cc}\clist
,并且#1
是,cc
而是。这绝对不是您想要的。#2
\clist
您想要的\@ifnextchar
是,提前查找下一个标记,并允许您根据找到的内容做出选择。
\documentclass{article}
\makeatletter
\newcommand{\mlist}{\@ifnextchar+{\mlist@add}{\mlist@def}}
\newcommand{\mlist@def}[1]{\def\clist{#1}}
\newcommand{\mlist@add}[2]{% #1 is +, discard it
\expandafter\def\expandafter\clist\expandafter{\clist,#2}%
}
\makeatother
\begin{document}
\def\clist{}
\mlist{cc}\clist\par% "cc" expected
\mlist+{dd}\clist\par % "cc,dd" expected
\end{document}
如果要添加在前面,策略可以如下:
\documentclass{article}
\makeatletter
\newcommand{\mlist}{\@ifnextchar+{\mlist@add}{\mlist@def}}
\newcommand{\mlist@def}[1]{\def\clist{#1}}
\newcommand{\mlist@add}[2]{% #1 is +, ignore it
\expandafter\mlist@prepend\expandafter{\clist}{#2}%
}
\newcommand{\mlist@prepend}[2]{\def\clist{#2,#1}}
\makeatother
\begin{document}
\def\clist{}
\mlist{cc}\clist\par % "cc" expected
\mlist+{dd}\clist\par % "dd,cc" expected
\end{document}
你的代码哪里错了?
在您的第一个调用中,\mlist{cc}\clist
您有cc
as#1
和\clist
as #2
。这已经非常危险了,因为您不知道 之后会发生什么\mlist{...}
。无论如何,您的\if#1+
测试结果是正确的,因为它是
\if cc+
所以你得到了
+\relax\edef\clist{\clist,\clist}
这是你不想要的,对吧?如果我们反转,因为\if+#1\relax
测试结果会是假的,所以\else
分支被采用,所以你会得到
\ifx\@nil\clist\def\clist{cc}\clist\else\mlist#1\@nil\fi\fi
(最后一个\fi
将在稍后通过宏扩展被删除)。此测试返回 false,因此您得到
\mlist cc\@nil\fi\fi
请注意,您丢失了括号。因此,让我们尝试通过添加括号来修复。现在您得到了
\if+cc\relax\edef\clist{\clist,\@nil}\else\ifx\@nil\@nil\def\clist{cc}\@nil\else\mlist{cc}\@nil\fi\fi\fi\fi
(我还删除了虚假的空格)。第一个测试是错误的,所以你得到
\ifx\@nil\@nil\def\clist{cc}\@nil\else\mlist{cc}\@nil\fi\fi\fi\fi
这次测试是正确的,其余的一切都会被忽略。
现在我们来检查第二个调用,即
\mlist+{dd}
这#1
是+
和#2
是dd
,因此(使用已经提到的修复)你得到
\if++\relax\edef\clist{\clist,dd}\else...\fi
之后的部分\else
无关紧要,因为测试结果是正确的。
所以应该是
\long\def\mlist#1#2{%
\if#1+\relax \edef\clist{\clist,#2}%append #2 to \clist
\else
\ifx\@nil#2%
\def\clist{#1}#2%redefine \clist
\else
\mlist{#1}\@nil
\fi
\fi
}
但它不是最好的方法。请仔细观察角色的位置%
。
可能更好的方法
我使用可选参数(用于管理更多列表)和“修饰”来定义\mlist
。请参阅示例,但本质上
\mlist<{a}
\mlist>{b}
分别附加或添加到列表。
我还添加了从列表中提取项目并计算其长度的代码。
\documentclass{article}
\ExplSyntaxOn
\NewDocumentCommand{\mlist}{ O{default} e{<>} }
{
\clist_if_exist:cF { l_lyl_mlist_#1_clist } { \clist_new:c { l_lyl_mlist_#1_clist } }
\lyl_mlist_append:nnn { #1 } { #2 } { #3 }
}
\NewExpandableDocumentCommand{\mlistitem}{ O{default} m }
{
\clist_item:cn { l_lyl_mlist_#1_clist } { #2 }
}
\NewExpandableDocumentCommand{\mlistcount}{ m }
{
\clist_count:c { l_lyl_mlist_#1_clist }
}
\NewDocumentCommand{\mlistprint}{ O{default} }
{
\clist_use:cn { l_lyl_mlist_#1_clist } { , }
}
\cs_new_protected:Nn \lyl_mlist_append:nnn
{
\clist_put_left:cx { l_lyl_mlist_#1_clist }
{
\tl_if_novalue:nF { #2 } { \exp_not:n { #2 } }
}
\clist_put_right:cx { l_lyl_mlist_#1_clist }
{
\tl_if_novalue:nF { #3 } { \exp_not:n { #3 } }
}
}
\ExplSyntaxOff
\begin{document}
\mlist>{cc}
\mlistprint % "cc" expected
\mlist<{bb}
\mlistprint % "bb,cc" expected
\mlist>{dd}
\mlistprint % "bb,cc,dd" expected
\mlist<{a}>{e}
\mlistprint % "a,bb,cc,dd,e" expected
\mlistcount{default} % 5 expected
\mlistitem{4} % "dd" expected
\mlist[new]
\mlist[new]<{xyz}
\mlist[new]>{abc}
\mlistprint[new] % "xyz,abc" expected
\mlistcount{new} % 2 expected
\mlistitem[new]{1} % "xyz" expected
\end{document}
答案2
你可以简单地使用 TeX 原语来完成你的任务:
\def\mlist {\futurelet\next\mlistA}
\def\mlistA {\ifx\next+\expandafter\mlistB \else\expandafter\mlistC \fi}
\def\mlistB +#1{\expandafter\def\expandafter\clist\expandafter{\clist,#1}}
\def\mlistC #1{\def\clist{#1}}
\def\clist{}
% test:
\mlist{cc}\clist\par% "cc" expected
\mlist+{dd}\clist\par % "cc,dd" expected