我遇到这样一种情况,我想要定义一个采用可变数量参数的命令,其中参数的数量可以通过编程方式知道\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)中通过一次编译使用。