为序列/列表/元组宏定义宏

为序列/列表/元组宏定义宏

我想定义一个宏,通过指定以下参数来定义进一步的宏来显示元素的序列/列表/元组:

  1. 新命令的名称。
  2. 空序列、列表或元组的显示内容。
  3. 如何打开单例序列/列表/元组。
  4. 如何关闭单例序列/列表/元组。
  5. 如何打开包含多个元素的序列/列表/元组。
  6. 如何关闭具有多个元素的序列/列表/元组。
  7. 在序列/列表/元组中两个元素之间显示什么作为分隔对象。

我查看了以下问题和相应的答案:

因此,我尝试编写命令,并得出以下两个版本(第二个版本在下面的代码中被注释掉了)。不幸的是,它们都不起作用,我不知道问题出在哪里:

\documentclass{article}

\makeatletter 
\newcommand{\definelistcommand}[7]{%
\expandafter\newcommand\csname @start#1\endcsname{\expandafter\@ifnextchar\csname @stop#1\endcsname{#2\csname @end#1\endcsname}{\csname @first#1\endcsname}}%
\expandafter\newcommand\csname @first#1\endcsname[1]{\expandafter\@ifnextchar\csname @stop#1\endcsname{#3##1#4\csname @end#1\endcsname}{#5##1\csname @next#1\endcsname}}%
\expandafter\newcommand\csname @next#1\endcsname[1]{#7##1\expandafter\@ifnextchar\csname @stop#1\endcsname{#6\csname @end#1\endcsname}{\csname @next#1\endcsname}}%
\expandafter\newcommand\csname @end#1\endcsname[1]{}% consumes the \stop command
\expandafter\newcommand\csname #1\endcsname[1]{\csname @start#1\endcsname##1\csname @stop#1\endcsname}%
}
\makeatother

% \makeatletter 
% \edef\definelistcommand#1#2#3#4#5#6#7{%
% \noexpand\newcommand\expandafter\noexpand\csname @start#1\endcsname{\noexpand\@ifnextchar\expandafter\noexpand\csname @stop#1\endcsname{#2\expandafter\noexpand\csname @end#1\endcsname}{\expandafter\noexpand\csname @first#1\endcsname}}%
% \noexpand\newcommand\expandafter\noexpand\csname @first#1\endcsname[1]{\noexpand\@ifnextchar\expandafter\noexpand\csname @stop#1\endcsname{#3##1#4\expandafter\noexpand\csname @end#1\endcsname}{#5##1\expandafter\noexpand\csname @next#1\endcsname}}%
% \noexpand\newcommand\expandafter\noexpand\csname @next#1\endcsname[1]{#7##1\noexpand\@ifnextchar\expandafter\noexpand\csname @stop#1\endcsname{#6\expandafter\noexpand\csname @end#1\endcsname}{\expandafter\noexpand\csname @next#1\endcsname}}%
% \noexpand\newcommand\expandafter\noexpand\csname @end#1\endcsname[1]{}% consumes the \stop command
% \noexpand\newcommand\expandafter\noexpand\csname #1\endcsname[1]{\expandafter\noexpand\csname @start#1\endcsname##1\expandafter\noexpand\csname @stop#1\endcsname}%
% }
% \makeatother

\definelistcommand{mylist}{empty}{[}{]}{(}{)}{,}

\begin{document}

\mylist{{a}{b}{c}}

\mylist{{a}{b}}

\mylist{a}

\mylist{{b}}

\mylist{}

\end{document}

期望的输出是:

(a,b,c)
(a,b)
[a]
[b]
empty

对于第一个版本,我收到此错误消息:

! Missing \endcsname inserted.
<to be read again>
                   \let
l.27 \mylist{{a}{b}{c}}

对于第二个版本,我得到了这个:

! Undefined control sequence.
l.27 \mylist
            {{a}{b}{c}}

我的命令中存在哪些错误以及正确的解决方案如何起作用?

编辑(向未来的读者指出,接受的答案并不是唯一有趣的答案):虽然我接受了 Christian Hupfer 的答案(因为它包含对我的尝试的解释和修复),但其他答案也包含有价值的贡献。特别是,我将使用 egreg 的解决方案,因为它允许使用键值对指定我想要的参数。不幸的是,我不能接受两个答案。

答案1

这是一个expl3带有变量的版本seq

该命令\NewListCommand声明了与第一个参数同名的命令列表宏,并使用了seq带有某些前缀的全局变量,参见\g_cryingshadow_mynewlist_seq等等。

这是由\mynewlist命令填充的,并且根据从#3到#6的其他参数的内容显示内容。第7个参数用作分隔符

一些注意事项:该empty参数可以是可选的。

\documentclass{article}


\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\NewListCommand}{mmmmmmm}{%
  \seq_new:c {g_cryingshadow_#1_seq} % Make a new sequence
  \expandafter\NewDocumentCommand\csname #1\endcsname{m}{%
    \seq_gclear:c {g_cryingshadow_#1_seq}% Clear the sequence
    \seq_gclear:N \l_tmpa_seq % Clear a temporary `\l_tmpa_seq
    \seq_set_split:Nnn \l_tmpa_seq {} {##1} % Split the sequence into tokens. 
    \seq_set_eq:cN {g_cryingshadow_#1_seq} \l_tmpa_seq % copy the temporary sequence to the real one → this would not be necessary if the sequence is not be used outside 
    \seq_if_empty:cTF {g_cryingshadow_#1_seq} {%
      #2% Checked --> is empty
    }{%
      % Display the various styles
      \int_case:nn { \seq_count:c {g_cryingshadow_#1_seq} }
      {%
        {1} {#3      \seq_use:cnnn {g_cryingshadow_#1_seq} {,} {#7} {#7} #4}
      }%
      \int_compare:nNnT { \seq_count:c {g_cryingshadow_#1_seq}}  > 1 {%
        #5      \seq_use:cnnn {g_cryingshadow_#1_seq} {#7} {#7} {#7} #6
      }
    }
  }
}
\ExplSyntaxOff


\NewListCommand{mynewlist}{empty}{[}{]}{(}{)}{,}


\begin{document}

\mynewlist{{A}{B}{C}}

\mynewlist{{A}{B}}

\mynewlist{{a}}

\mynewlist{}


\mynewlist{{A}{B}{C}{D}{E}{F}}


Astronomer's alphabet:

\mynewlist{{Oh}{be}{a}{fine}{girl}{kiss}{me}}

\end{document}

在此处输入图片描述

编辑:这是通常的 LaTeX 的工作版本:

\documentclass{article}
\usepackage{etoolbox}

\makeatletter

\def\definelistcommand#1#2#3#4#5#6#7{%

  \expandafter\def\csname @firstinlist#1\endcsname##1{%
    \@ifnextchar\@stoplist{%
      #3##1#4\@endlist%
    }{%
      #5##1\csname @nextlist#1\endcsname%
    }%
  }

  \expandafter\def\csname @nextlist#1\endcsname##1{%
    \@ifnextchar\@stoplist{%
      #7##1#6\@endlist%
    }{%
      #7##1\csname @nextlist#1\endcsname%
    }%
  }


  \expandafter\def\csname @startlist#1\endcsname##1{%
    \@ifnextchar\@stoplist{%
      #3##1#4\@endlist%
    }{%
      \csname @firstinlist#1\endcsname##1% 
    }%
  }

  \expandafter\def\csname #1\endcsname##1{%
    \ifblank{##1}{%
      #2%
    }{%
      \csname @startlist#1\endcsname##1\@stoplist%
    }%
  }
}

\def\@endlist#1{}


\definelistcommand{mycmd}{empty}{[}{]}{(}{)}{,}

\definelistcommand{othercmd}{empty}{\textbraceleft}{\textbraceright}{(}{)}{+}

\begin{document}


\makeatother

\mycmd{{A}{B}{C}}

\mycmd{A}

\mycmd{{A}{B}}

\mycmd{}

\othercmd{{O}{B}}

\othercmd{{O}}


\end{document}

答案2

你可以尝试这个宏:

\newcount\tmpnum
\def\definelistcommand#1#2#3#4#5#6#7{%
   \expandafter\def\csname\string#1:list\endcsname##1{%
      \ifcase##1 #2\or#3\or#4\or#5\or#6\or#7\fi}%
   \def#1{\dolist#1}%
}
\def\dolist#1#2{\tmpnum=0 \def\tmp{}%
   \def\listcommand{\csname\string#1:list\endcsname}%
   \dolistA#2\end}
\def\dolistA#1{%
   \ifx\end#1%
      \ifnum\tmpnum=0 \listcommand0%
      \else\ifnum\tmpnum=1 \listcommand1\tmp\listcommand2%
      \else \listcommand3\tmp\listcommand4%
      \fi\fi
   \else\edef\tmp{\ifx\tmp\empty\else\tmp\listcommand5\fi#1}%
      \advance\tmpnum by1
      \expandafter\dolistA\fi
}

\definelistcommand \mylist {empty} [](),

\mylist{{a}{b}{c}}

\mylist{{a}{b}}

\mylist{a}

\mylist{{b}}

\mylist{}

解释。\definelistcommand\foo定义了两个控制序列。第一个\\foo:list\ifcase。它扩展了适当的声明参数:\\foo:list0扩展到nothing\\foo:list1[。第二个定义的控制序列是\foo。这是宏\dolist\foo

的用法\foo{text}扩展为\dolist\foo{text}。此宏重置\tmp为空,\tmpnum为零并保存\listcommand\\foo:list。然后它运行\dolistA text\end

重复读取\dolistA文本中的项。它\tmp使用 将这些项保存到宏中\edef\tmp。如果不为空,则在前一个内容和下一个项之间添加\tmp逗号(别名) 。计算项数。当达到 时,根据值打印。\listcommand5#1\tmpnum\end\tmp\tmpnum

请注意,我们不需要任何外部包,我们可以非常简单地声明类似列表的宏。

答案3

我认为,与使用一长串参数相比,使用键值语法更好。

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\definelistcommand}{mm}
 {% #1 is the command name, #2 is the key-value setup
  % clear the temporary property list
  \prop_clear:N \l_cryingshadow_list_temp_prop
  \keys_set:nn { cryingshadow/list } { #2 }
  % check if left-single or right-single have been set
  \prop_if_in:NnF \l_cryingshadow_list_temp_prop { left-single }
   {
    \prop_put:Nnx \l_cryingshadow_list_temp_prop { left-single }
     { \prop_item:Nn \l_cryingshadow_list_temp_prop { left } }
   }
  \prop_if_in:NnF \l_cryingshadow_list_temp_prop { right-single }
   {
    \prop_put:Nnx \l_cryingshadow_list_temp_prop { right-single }
     { \prop_item:Nn \l_cryingshadow_list_temp_prop { right } }
   }
  % allocate a specific property list and make it equal to the temporary one
  \prop_new:c { g_cryingshadow_list_#1_prop }
  \prop_gset_eq:cN { g_cryingshadow_list_#1_prop } \l_cryingshadow_list_temp_prop
  % define the runtime macro to call the generic one with the current name as argument
  \cs_new_protected:cpn { #1 } ##1 { \cryingshadow_list_print:nn { #1 } { ##1 } }
 }

% allocate some variables
\prop_new:N \l_cryingshadow_list_temp_prop
\seq_new:N \l_cryingshadow_list_input_seq

% syntactic sugar
\cs_new_protected:Nn \__cryingshadow_list_put:nn
 {
  \prop_put:Nnn \l_cryingshadow_list_temp_prop { #1 } { #2 }
 }
\cs_new_protected:Nn \__cryingshadow_list_item:nn
 {
  \prop_item:cn { g_cryingshadow_list_#1_prop } { #2 }
 }

% define the keys
\keys_define:nn { cryingshadow/list }
 {
  empty .code:n = \__cryingshadow_list_put:nn { empty } { #1 },
  left-single .code:n = \__cryingshadow_list_put:nn { left-single } { #1 },
  right-single .code:n = \__cryingshadow_list_put:nn { right-single } { #1 },
  left .code:n = \__cryingshadow_list_put:nn { left } { #1 },
  right .code:n = \__cryingshadow_list_put:nn { right } { #1 },
  delimiter .code:n = \__cryingshadow_list_put:nn { delimiter } { #1 },
 }

\cs_new_protected:Nn \cryingshadow_list_print:nn
 {
  % split the input into items at commas
  \seq_set_split:Nnn \l_cryingshadow_list_input_seq { , } { #2 }
  % branch between empty or single and multiple items
  \int_compare:nTF { \seq_count:N \l_cryingshadow_list_input_seq = 1 }
   {
    % if the sequence has just one item it could be empty
    \tl_if_blank:nTF { #2 }
     { \__cryingshadow_list_item:nn { #1 } { empty } }
     {
      \__cryingshadow_list_item:nn { #1 } { left-single }
      #2
      \__cryingshadow_list_item:nn { #1 } { right-single }
     }
   }
   {
    % otherwise it has multiple items
    \__cryingshadow_list_item:nn { #1 } { left }
    \seq_use:Nn \l_cryingshadow_list_input_seq { \__cryingshadow_list_item:nn { #1 } { delimiter } }
    \__cryingshadow_list_item:nn { #1 } { right }
   }
 }

\ExplSyntaxOff

\definelistcommand{firstlist}{
  empty=empty,
  left-single=[,
  right-single=],
  left=(,
  right=),
  delimiter={, },
}
\definelistcommand{secondlist}{
  empty=nothing,
  left=\{,
  right=\},
  delimiter={--},
}

\begin{document}

\firstlist{A,B,C}

\firstlist{A,B}

\firstlist{a}

\firstlist{}

\secondlist{A,B,C}

\secondlist{A,B}

\secondlist{a}

\secondlist{}

\end{document}

在此处输入图片描述

这是如何运作的?

\definelistcommand一个设置了一个特定于要定义的列表命令的属性列表,其中存储了各个项目(外部分隔符、内部分隔符、空情况的处理方式);然后它定义宏来调用通用函数来产生输出。

通过检查第二个参数为属性列表提供值。如果未指定left-single或,则提供或的right-single值。leftright

在调用时,通用宏从特定属性列表中获取值,用逗号分隔参数,然后分支:如果参数为空,则打印案例的值;如果只有一项,则使用left-singleright-single外部分隔符;否则使用leftright,并在项之间使用内部分隔符。

相关内容