可以接受可变数量参数的命令

可以接受可变数量参数的命令

我遇到这样一种情况,我想要定义一个采用可变数量参数的命令,其中参数的数量可以通过编程方式知道\count,并以某种方式处理参数(假设它们是一个列表)。

举个例子,假设我想将参数输出为逗号分隔的列表。

\newcommand{\makecsv}[N]{#1, #2, ..., #N}

我想到的执行此类操作的代码(以通用的方式)本质上是接受一个命令,\csv并以递归方式将其扩展 N 次。\csv需要知道如何继续递归,并且具有一些我想通过递归进行线程化的状态(而不是使用\global)。

\documentclass{report}
\usepackage{etoolbox}

\makeatletter
\newcommand{\ifzero}[3]{%
  % #1: count
  % #2: state for #3
  % #3: macro to expand to
  % - should take at least 2 parameters
  % - ##1: count threaded through
  % - ##2: macro state threaded through
  \ifnum\c > 0
    \def\tmp@f##1##2##3{##1{##2}{##3}}%
    \advance#1 -1%
  \else
    \def\tmp@f##1##2##3{)}% note closeparen here (could be param)
  \fi
  \tmp@f{#3}{#1}{#2}%
}
\makeatother

\newcommand{\csv}[3]{
  % #1: count
  % #2: separator state
  % #3: string to concat
  % 
  #2#3\ifzero{#1}{, }{\csv}%
}

\newcommand{\makecsv}[1]{%
  \ifzero{#1}{}{\csv}%
}

\makeatletter
\newcommand{\decl}[3]{%
  % #1: decl id
  % #2: decl symbol
  % #3: # params
  \csgdef{decl@#1}{#2}%
  \global\expandafter\newcount\csname decl@#1@nparams\endcsname%
  \csuse{decl@#1@nparams} #3\relax%
}

\newcommand{\usedecl}[1]{%
  \newcount\c
  \c \the\csuse{decl@#1@nparams}
  \csuse{decl@#1}(\makecsv{\c}%
}
\makeatother

% declare some interface routines
\decl{foo}{FOO}{3}
\decl{bar}{BAR}{4}

\begin{document}
\usedecl{foo}{p1}{p2}{p3}\par
\usedecl{bar}{p1}{p2}{p3}{p4}\par
\end{document}

在 2e 中这样做合理吗,或者是否存在通常使用的某种标准方法?

编辑1

似乎我原来的 MWE 不足以描述为什么有人会想要这个。我已经用用例更新了 MWE。\decl允许作者以声明方式定义 C 样式函数,并\usedecl允许作者生成该函数的用法,并将其参数绑定到特定参数。

这与我正在做的事情足够相似,它应该有助于激发这个例子。

答案1

正如评论所说,这里有一个使用的解决方案\@ifnextchar。我还对参数过多或过少进行了检查(或者为什么它们由用户提供?)。

\@ifnextchar(或其“非常内部的”大哥)跳过空格\kernel@ifnextchar,导致第三和第四个例子中的空格被删除。

代码

\documentclass{report}
\usepackage{etoolbox}
\makeatletter
\newcommand*{\decl}[3]{%
  % #1: decl id
  % #2: decl symbol
  % #3: # params
  \csdef{decl@symbol@#1}{#2}%
  \expandafter\newcount\csname c@decl@params@#1\endcsname
  \csuse{c@decl@params@#1}=#3\relax
}
\newcount\decl@params@check
\newcommand*{\usedecl}[1]{%
  \def\decl@name{#1}%
  \edef\decl@params{\the\csuse{c@decl@params@#1}}%
  \def\decl@symbol{\csuse{decl@symbol@#1}}%
  \decl@params@check=\z@
  \let\decl@list\@gobble % the \@gobble removes the first , (expandable)
  \def\decl@next{\kernel@ifnextchar\bgroup\use@decl\use@decl@finish}%
  \decl@next
}
\newcommand*{\use@decl}[1]{%
  \advance\decl@params@check\@ne
  \expandafter\ifnum\the\decl@params@check>\decl@params\relax % too many!
    \PackageWarning{decl}{You have used more params than the \decl@name\space function expected!
                                                    I ignore this (and any following) param, ok?}% but insert the extra argument anyway?!
     \def\decl@next{\use@decl@finish{#1}}% the extra pair of braces {} keeps '#1' local as it is in the input stream
  \else
    \expandafter\def\expandafter\decl@list\expandafter{\decl@list\decl@list@sep#1}%
  \fi
  \decl@next
}
\newif\ifuse@decl@message
\newcommand*{\use@decl@finish}{%
  \ifnum\decl@params@check<\decl@params\relax % too few!
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {%
    \ifuse@decl@message\else
      \PackageWarning{decl}{You have used fewer params than the \decl@name\space function expected! I'm filling up with '??'!}%
      \use@decl@messagetrue
    \fi
    \use@decl{??}}
  {%
    \decl@symbol\decl@list@start\decl@list\decl@list@end
    \use@decl@messagefalse
  }%
}
\newcommand*{\setdeclstart}[1]{\def\decl@list@start{#1}}
\newcommand*{\setdeclend}[1]{\def\decl@list@end{#1}}
\newcommand*{\setdeclsep}[1]{\def\decl@list@sep{#1}}
\makeatother

\setdeclstart{(}
\setdeclend{)}
\setdeclsep{, }

% declare some interface routines
\decl{foo}{FOO}{3}
\decl{bar}{BAR}{4}

\begin{document}
given $P$, $Q$ and $R$ such \dots\ that $\usedecl{foo}{P}{Q}{R}$ results in \dots\par
given P, Q and R such \dots\ that \usedecl{foo}{P}{Q}{R}\ results in \dots\par
\usedecl{foo}{p1}{p2}{p3}\par
\usedecl{bar}{p1}{p2}{p3}{p4}\par
\usedecl{bar}{p1} foo\par
\usedecl{foo}{p1}{p2}{p3} {p4}\par
\end{document}

输出

在此处输入图片描述

答案2

如果您愿意提前了解,您可以检查是否存在“另一个参数”并继续即时吞噬它们:

在此处输入图片描述

\documentclass{article}
\usepackage{etoolbox}% http://ctan.org/pkg/etoolbox

\makeatletter
\newcommand{\newdecl}[2]{\csgdef{decl@#1}{#2}}% Creates a declaration
\newcommand{\csvdel}{}% Delimiter used in CSV representation
\newcommand{\newusedecl}[2][,]{% Use a declaration
  \renewcommand{\csvdel}{\renewcommand{\csvdel}{#1\,}}% Delay \csvdel one cycle.
  \csname decl@#2\endcsname(\checknextarg}
\newcommand{\checknextarg}{\@ifnextchar\bgroup{\gobblenext}{}}% Check if another "argument" exists
\newcommand{\gobblenext}[1]{\csvdel#1\@ifnextchar\bgroup{\gobblenext}{)}}% Gobble next "argument"
\makeatother

% declare some interface routines
\newdecl{foo}{FOO}
\newdecl{bar}{BAR}

\begin{document}

\newusedecl{foo}{p1}{p2}{p3}\par
\newusedecl{bar}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}
  {p1}{p2}{p3}{p4}\par
\newusedecl[;]{foo}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}
  {p1}{p2}{p3}{p4}

\end{document}

使用 可以\@ifnextchar查看。有关此内容的解释,请参阅理解\@ifnextchar. 延迟使用\csvdel(CSV 分隔符)源于狡猾的 (La)TeX 技巧)。

\newusedecl适应的可选参数\csvdel

答案3

您没有给我们太多的参考,但这里有一些东西似乎可以实现您想要的东西,同时输入一个逗号分隔的列表(而不是传递可变数量的参数)。

\documentclass{article}
\usepackage{xparse}
\newcounter{myargcounter}
\ExplSyntaxOn
\clist_new:N \l_myvararg_parameters_clist
\tl_new:N    \l_myvararg_current_item_tl
\NewDocumentCommand{\makecsv}{ m }
    {
        \clist_set:Nn \l_myvararg_parameters_clist { #1 }
        \int_while_do:nNnn { \clist_count:N \l_myvararg_parameters_clist } > { 1 }
            {
                \clist_pop:NN \l_myvararg_parameters_clist \l_myvararg_current_item_tl
                \tl_use:N \l_myvararg_current_item_tl,
            }   
         \clist_pop:NN \l_myvararg_parameters_clist
            \l_myvararg_current_item_tl
         {} ~ and ~ \tl_use:N \l_myvararg_current_item_tl
    }
\ExplSyntaxOff
\pagestyle{empty}
\begin{document}
\makecsv{a,b,c,d}

\makecsv{a,b,c,d,e,f,g}

\makecsv{a,b}

\end{document}

答案4

使用我的新 Python 库(目前在 CTAN 上)从 TeX 执行 Python 代码...

%! TEX program = lualatex
\documentclass{report}
\usepackage{pythonimmediate}

\begin{pycode}
import pythonimmediate

declared_commands = {}  # global variable

@pythonimmediate.newcommand
def decl():
    name = pythonimmediate.get_arg_str()
    symbol = pythonimmediate.get_arg_str()
    num_param = int(pythonimmediate.get_arg_str())
    declared_commands[name] = symbol, num_param

@pythonimmediate.newcommand
def usedecl():
    name = pythonimmediate.get_arg_str()
    symbol, num_param = declared_commands[name]
    args = [pythonimmediate.get_arg_str() for _ in range(num_param)]
    return symbol + "(" + ", ".join(args) + ")"

@pythonimmediate.newcommand
def withoutdecl():
    symbol = pythonimmediate.get_arg_str()
    num_param = int(pythonimmediate.get_arg_str())
    args = [pythonimmediate.get_arg_str() for _ in range(num_param)]
    return symbol + "(" + ", ".join(args) + ")"
\end{pycode}


% declare the symbols and the amounts of parameters:
\decl{foo}{FOO}{3}
\decl{bar}{BAR}{4}

\begin{document}

\usedecl{foo}{p1}{p2}{p3}\par
\usedecl{bar}{p1}{p2}{p3}{p4}\par
\withoutdecl{BAZ}{5}{p1}{p2}{p3}{p4}{p5}\par

\end{document}

模板是从 Ulrich 的答案复制而来的,输出相同:

输出

据我所知,我相信这应该比使用任何现有的 TeX Python 绑定库的任何解决方案更容易理解。

它不可扩展。(在 LuaTeX 中,这个问题可以很容易地解决。)并且需要--shell-escape。但除此之外,可以在任何引擎(以及 Overleaf)中通过一次编译使用。

相关内容