我想定义一个宏( )来根据可选参数macrob
改变另一个宏()的行为。macroa
IfNoValueTf
我以前使用或之类的方法做过这件事\IfBooleanTF
,但这种情况有点棘手,因为我希望能够传递可选的键值对。
签名将是{sO{}mO{}}
密钥的第二个参数。
基本上:
\macrob*{expr}
或\macrob*{expr}[opt]
应该等同于\macroa*{\expr}{opt|default}
,忽略的可选参数\macrob
。\macrob[key1=val1]{expr}[opt]
应该等同于\macroa[val1]{expr}{opt}
,忽略第二个键。\macrob[key2=true]{expr}[opt]
应该等同于\macroa{expr}{opt}
。\macrob[key2=true]{expr}
应该等同于\macroa{expr}{}
(按照O{}
)。\macrob{expr}[opt]
应该等同于\macroa{expr}{opt}
。\macrob{expr}
应该等同于expr
。
如果没有key2
,我就会选择
\NewDocumentCommand\macrob{somO{}}{%
\IfBooleanTF{#1}{\macroa*{#3}{#4}}{%
\IfNoValueTF{#2}{\macroa[#2]{#3}{#4}}{\macroa{#3}{#4}}}
但我不知道如何为键设置默认布尔行为(\bool_set_false:N
?),或者是否可以将这些逻辑结构与 expl3 语法混合。
如果有帮助的话,Python 模型看起来应该是这样的
def macroa(arg1, arg2, star=None, opt=None):
# do things with arg1 and arg2, maybe star or opt
def macrob(man_arg, star=None, opt_arg=None, key1=None, key2=False):
if star:
return macroa(man_arg, opt_arg, True) # case 1
else:
if key1:
return macroa(man_arg, opt_arg, opt=key1) # case 2
else:
if key2 or opt_arg:
return macroa(man_arg, opt_arg) # case 3, 4, 5
else:
return man_arg # case 6
编辑:我将目标签名从 改为{sO{}mO{}}
({sO{}mO{}}
星号,可选键值对,强制,可选且默认)。 改为o
是一个错误,应该从一开始{somO{}}
就改成。O{}
的签名\macroa
是{somm}
。事实上,案例 1 应该读作\macroa*{\expr}{opt}
(已修复) 而不是\macroa*{\expr}[opt]
。
抱歉,我的问题不够清楚。
这是该问题的更详细版本。
目标是能够排版带有括号中参数的函数,并可以选择调整分隔符的大小,而无需诉诸\left(\right)
,这往往会在左括号前插入一些额外的空格。我为此使用\DeclarePairedDelimiterXPP
from mathtools
。但是,有时我只想打印与函数关联的符号,而不带参数。或者,有时我想在括号之间使用占位符来打印它。这就是我得到的结果,改编了 Skillmon 的答案。
\DeclarePairedDelimiterXPP\funcarg[2]{#1}\lparen\rparen{}{#2}
\ExplSyntaxOn
\cs_set:Npn \__parseiv:n #1 {%
\bool_lazy_any:nTF {{\tl_if_empty_p:n {#1}} {\tl_if_blank_p:n {#1}}} {\cdot} {#1}
}
\keys_define:nn {nmt/func} {%
size .tl_set:N = \l__nmt_size,
ph .bool_set:N = \l__nmt_ph
}
\NewDocumentCommand\funcgen{sO{}mO{}}{%
\IfBooleanTF{#1}{%
\funcarg*{#3}{\__parseiv:n {#4}}
}{%
\group_begin:
\keys_set:nn {nmt/func} {#2}
\tl_if_empty:NTF \l__nmt_size {%
\bool_if:NTF \l__nmt_ph {%
\funcarg{#3}{\__parseiv:n{#4}}%
}{%
\tl_if_empty:nTF {#4} {#3} {\funcarg{#3}{\__parseiv:n {#4}}}%
}%
}{%
\exp_last_unbraced:NNo \funcarg[\l__nmt_size]{#3}{\__parseiv:n {#4}}%
}
\group_end:
}%
}
\ExplSyntaxOff
\NewDocumentCommand\Gm{sO{}m}{\IfBooleanTF{#1}{\funcgen*}{\funcgen}[#2]{\Gamma}[#3]}
结果示例:
\[\funcgen{f}[] \quad ; \quad \funcgen[ph=true]{f} \quad ; \quad \funcgen[ph=true,size=\Big]{f} \quad ; \quad \funcgen[ph=false,size=\Big]{f}\]
\[\funcgen{f}[x] \quad ; \quad \funcgen[ph=false]{f}[x]\]
\[\funcgen*{f} \quad ; \quad \funcgen*{f}[\frac{x}{2}] \quad ; \quad \funcgen*[size=\big]{f}[\frac{x}{2}]\]
\[f(\frac{x}{2}) \quad ; \quad f\left(\frac{x}{2}\right) \quad ; \quad \funcgen[size=\Big]{f}[\frac{x}{2}] \quad ; \quad \funcgen[size=\big]{f}[\frac{x}{2}]\]
\[\Gamma(\frac{\beta}{2}) \quad ; \quad \Gamma\left(\frac{\beta}{2}\right) \quad ; \quad \funcgen*{\Gamma}[\frac{\beta}{2}] \quad ; \quad \funcgen[size=\Big]{\Gamma}[\frac{\beta}{2}]\]
\[\Gm{} \quad ; \quad \Gm{\frac{\beta}{2}} \quad ; \quad \Gm*{\frac{\beta}{2}} \quad ; \quad \funcgen[ph=true]{\Gamma}[]\]
就在那时,我意识到我可以完全废弃这些密钥,而完全依靠是否调用星号版本并测试最后一个参数的值来实现我想要的效果。
\ExplSyntaxOn
\cs_set:Npn \__parseiv_ii:n #1 {%
\tl_if_blank:nTF {#1} {\cdot} {#1}
}
\NewDocumentCommand\funcgenii{somO{}}{%
\IfBooleanTF{#1}{%
\funcarg*{#3}{\__parseiv_ii:n {#4}}
}{%
\tl_if_novalue:nTF {#2} {%
\tl_if_empty:nTF {#4} {#3} {\funcarg{#3}{\__parseiv_ii:n {#4}}}%
}{%
\funcarg[#2]{#3}{\__parseiv_ii:n {#4}}%
}
}%
}
\ExplSyntaxOff
\NewDocumentCommand\Gmii{som}{\IfBooleanTF{#1}{\funcgenii*}{\funcgenii}[#2]{\Gamma}[#3]}
更短,可能更容易阅读,使用起来也更直接。与上面相同的例子变成
\[\funcgenii{f}[] \quad ; \quad \funcgenii*{f} \quad ; \quad \funcgenii[\Big]{f} \quad ; \quad \funcgenii*[\Big]{f}\]
\[\funcgenii{f}[x]\]
\[\funcgenii*{f}[\frac{x}{2}] \quad ; \quad \funcgenii*[\big]{f}[\frac{x}{2}] \quad ; \quad \funcgenii[\big]{f}[\frac{x}{2}]\]
\[f(\frac{x}{2}) \quad ; \quad f\left(\frac{x}{2}\right) \quad ; \quad \funcgenii[\Big]{f}[\frac{x}{2}]\]
\[\Gamma(\frac{\beta}{2}) \quad ; \quad \Gamma\left(\frac{\beta}{2}\right) \quad ; \quad \funcgenii*{\Gamma}[\frac{\beta}{2}] \quad ; \quad \funcgenii[\Big]{\Gamma}[\frac{\beta}{2}]\]
\[\Gmii{} \quad ; \quad \Gmii{\frac{\beta}{2}} \quad ; \quad \Gmii[\big]{\frac{\beta}{2}} \quad ; \quad \Gmii*{\frac{\beta}{2}} \quad ; \quad \funcgenii*{\Gamma}[]\]
\Gm
正如我对和的定义\Gmii
所示,目标是将其用作其他函数的模板,这些函数必须定义强制参数。我对在传递给( )\genfunc
之前测试星号的方式不太满意,我想知道是否可以改进。\genfunc
\IfBooleanTF{#1}{\funcgenii*}{\funcgenii}[#2]{\Gamma}[#3]
也欢迎其他评论!
答案1
如果我理解正确的话,您想控制用于函数参数的括号的大小。您建议的语法不适合人类,但适合机器。我们经常忘记 TeX 源代码是为人类设计的,即人类在文本编辑器中创建它们,人类在检查源代码时阅读它们。语法必须为人类设计。这意味着,如果我想打印 f(x),那么我f(x)
在源代码中写入时不需要任何可选参数、键/值设置、带星号的宏版本等。
当然,如果用户想要打印 f(x(y+z)),那么他显然希望外括号更大。这f\bigl(x(y+z)\bigr)
是正确的,但从人眼的角度来看它看起来很丑。在我看来,这就是你的想法的开始。但你的语法并不让人舒服。它看起来好多了,可以\bigp f(x(y+z))
扩展到前面的例子。
我建议使用宏\bigp
、\Bigp
、\biggp
,\Biggp
它们可以用作函数符号前的前缀。以下参数可以用(...)
、 或[...]
或包围\{...\}
,{...}
并且给定的括号会更大地打印。例如,\Bigp F[x+y]
打印的内容与 相同F\Bigl[x+y\bigr]
。函数的参数文本被解释为相对于给定括号的平衡文本。此外,参数有自己的组,因此可以编写\Bigp R(a\over b+c)
并打印
R\Bigl({a\over b+c}\Bigr)
。我们为人类设计语法,而不是为机器设计语法。
实现如下:
\def\bigp#1{#1\fparam\bigl\bigr}
\def\Bigp#1{#1\fparam\Bigl\Bigr}
\def\biggp#1{#1\fparam\biggl\biggr}
\def\Biggp#1{#1\fparam\Biggl\Biggr}
\def\autop#1{#1\fparam\left\right} % for auto-sized parentheses
\def\normalp#1{#1\fparam\relax\relax} % for no-scaled parenthesed
\def\fparam#1#2{\let\bigleft=#1\let\bigright=#2\futurelet\next\fparamA}
\def\fparamA{%
\ifx\next(\fparamB()\fi
\ifx\next[\fparamB[]\fi
\ifx\next\{\fparamB\{\}\fi
\ifx\next\bgroup \def\lparen{\{}\def\rparen{\}}\afterrelax{\fparamC}\fi
\relax
}
\def\fparamB#1#2#3\relax{\fi
\def\lparen{#1}\def\rparen{#2}%
\def\next#1##1#2{\ensurebalanced#1#2\fparamC{##1}}%
\next
}
\def\fparamC#1{%
\ifx\bigleft\left \mathopen{}\fi
\bigleft\lparen{#1}\bigright\rparen
\ifx\bigright\right \mathclose{}\fi
}
\def\afterrelax#1#2\relax{\fi#1}
我们需要一个宏\ensurebalanced
来实现使用不同括号来扫描平衡文本的功能{...}
。我借用了OpTeX 技巧 0043经过微小改动,它也可以适用于 pdfTeX。
\newcount\tmpnum
\def\ensurebalanced#1#2#3{%
\def\balopen{#1}\def\balclose{#2}\let\balaction=#3%
\def\readnextbal##1##2#2{\ensurebalancedA{##1#2##2}}%
\ensurebalancedA}
\def\ensurebalancedA#1{\isbalanced#1%
\iftrue\afterfi{\balaction{#1}}\else\afterfi{\readnextbal{#1}}\fi}
\def\isbalanced#1\iftrue{\tmpnum=0 \isbalancedA#1{\isbalanced}}
\def\isbalancedA#1#{\countbalanced#1\isbalanced \isbalancedB}
\def\isbalancedB#1{%
\ifx\isbalanced#1\afterfi{\csname ifnum\endcsname\tmpnum=0 }\else\expandafter\isbalancedA\fi}
\def\countbalanced#1{\expandafter\ifx\balopen #1\advance\tmpnum by1 \fi
\expandafter\ifx\balclose#1\advance\tmpnum by-1 \fi
\ifx\isbalanced#1\else\expandafter\countbalanced\fi}
\def\afterfi#1#2\fi{\fi#1}
现在,我们可以做与您类似的测试:
$$
f(x(y+z)), \quad \bigp f(x(y+z)),\quad \autop f(a\over b), \quad \Bigp f(a\over b+c),
$$
$$
f[x(y+z)], \quad \bigp f[x(y+z)],\quad \autop f[a\over b], \quad \Bigp f{a\over b+c},
$$
$$
f ; \quad f(\cdot) ; \quad \Bigp f(\cdot) ;
$$
$$
f(x)
$$
$$
f({x\over2}) ; \quad \normalp f(x\over2) ; \quad \autop f(x^2\over2) ; \quad \Bigp f(x^2\over2)
$$
$$
\Gamma ({\beta\over2}) ; \quad \autop\Gamma (\beta\over2) ; \quad \Bigp\Gamma (\beta\over2)
$$
$$
\Gamma ; \quad \normalp\Gamma (\beta\over2) ; \quad \autop\Gamma (\beta\over2) ; \quad \Gamma (\cdot)
$$
结果如下:
答案2
这个答案提出了一些解决方案,其中两个使用我自己的expkv
-family 软件包,第三个使用l3keys
模块expl3
。
expkv-cs
以下使用expkv-cs
您的 key=value 接口,因为它设置起来非常简单,并且您可以将键值作为宏参数获取。
我将您的\macrob
签名更改为sO{}mo
,否则(在 Python 模型中)if key2 or opt_arg
无法检查,并且如果接口只获取一个空值,则 key=value 方法需要的检查较少,而不需要使用另一个 进行检查\IfNoValueTF
。否则,这几乎只是一堆测试某个值是否存在的测试,由于 的无赋值性质是expkv-cs
作为key2
-type 实现的enum
,这意味着true
被转发为1
和,然后使用false
对其0
进行评估\ifodd
。
\documentclass{article}
\usepackage{expkv-cs}
\makeatletter
\NewDocumentCommand \macroa { somm }
{%
\begin{tabular}{@{}ll@{}}
\hline
& \IfBooleanF{#1}{No~}Star \\
Opt: & #2 \\
Arg1: & #3 \\
Arg2: & #4 \\
\hline
\end{tabular}%
}
\NewDocumentCommand \macrob { sO{}mo }
{%
\IfBooleanTF{#1}%
{%
\IfNoValueTF{#4}%
{\macroa*{#3}{}}%
{\macroa*{#3}{#4}}%
}%
{\macrob@KV{#2}{#3}{#4}}%
}
\expanded{\unexpanded{\ekvcSplitAndForward\macrob@KV\macrob@DO}%
{%
key1 = \csname c_novalue_tl\endcsname
,key2-internal = 0
}
}
\ekvcSecondaryKeys\macrob@KV
{
enum key2 = {key2-internal}{false,true}
}
\newcommand\macrob@DO[4]
{%
\IfValueTF{#1}%
{%
\IfNoValueTF{#4}%
{\macroa[#1]{#3}{}}%
{\macroa[#1]{#3}{#4}}%
}%
{%
\IfNoValueTF{#4}%
{%
\ifodd#2
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{\macroa{#3}{}}%
{#3}%
}%
{\macroa{#3}{#4}}%
}%
}
\begin{document}
\macrob*{expr}
\macrob[key1=val1]{expr}[opt]
\macrob[key2=true]{expr}[opt]
\macrob[key2=true]{expr}
\macrob{expr}[opt]
\macrob{expr}
\end{document}
expkv-def
另一种解决方案可以使用expkv-def
,它类似于l3keys
或其他更传统的 key=value 解决方案,支持不同的键类型。
\documentclass{article}
\usepackage{expkv-def}
\makeatletter
\NewDocumentCommand \macroa { somm }
{%
\begin{tabular}{@{}ll@{}}
\hline
& \IfBooleanF{#1}{No~}Star \\
Opt: & #2 \\
Arg1: & #3 \\
Arg2: & #4 \\
\hline
\end{tabular}%
}
\NewDocumentCommand \macrob { sO{}mo }
{%
\IfBooleanTF{#1}%
{%
\IfNoValueTF{#4}%
{\macroa*{#3}{}}%
{\macroa*{#3}{#4}}%
}%
{%
\begingroup % keeping the scope of the set keys local
\ekvset{macrob}{#2}%
\macrob@opt
{\macrob@opt@aux{#3}{#4}}%
{%
\IfNoValueTF{#4}%
{%
\macrob@bool
{\macroa{#3}{}}%
{#3}%
}%
{\macroa{#3}{#4}}%
}%
\endgroup
}%
}
\newcommand\macrob@opt@aux[3]
{%
\IfNoValueTF{#2}%
{\macroa[#3]{#1}{}}%
{\macroa[#3]{#1}{#2}}%
}
\ekvdefinekeys{macrob}
{
data key1 = \macrob@opt
,boolTF key2 = \macrob@bool
}
\begin{document}
\macrob*{expr}
\macrob[key1=val1]{expr}[opt]
\macrob[key2=true]{expr}[opt]
\macrob[key2=true]{expr}
\macrob{expr}[opt]
\macrob{expr}
\end{document}
l3keys
这类似于expkv-def
,但l3keys
与密钥类型没有任何可比性data
。相反,这会测试存储值的宏是否为空。这样,key1={}
与根本没有使用没有区别key1
。当然,您可以做一些类似于expkv-cs
变体的事情,并使用\c_novalue_tl
作为 的初始值key1
,然后可以使用\tl_if_eq:NNTF \c_novalue_tl \l__macrob_key_tl
来测试密钥是否被使用。
\documentclass{article}
\makeatletter
\NewDocumentCommand \macroa { somm }
{%
\begin{tabular}{@{}ll@{}}
\hline
& \IfBooleanF{#1}{No~}Star \\
Opt: & #2 \\
Arg1: & #3 \\
Arg2: & #4 \\
\hline
\end{tabular}%
}
\ExplSyntaxOn
\NewDocumentCommand \macrob { sO{}mo }
{%
\IfBooleanTF{#1}%
{%
\IfNoValueTF{#4}%
{\macroa*{#3}{}}%
{\macroa*{#3}{#4}}%
}%
{%
\group_begin:
\keys_set:nn { macrob } {#2}
\tl_if_empty:NTF \l__macrob_key_tl
{
\IfNoValueTF {#4}
{
\bool_if:NTF \l__macrob_key_bool
{ \macroa {#3} {} }
{ #3 }
}
{ \macroa {#3} {#4} }
}
{
\IfNoValueTF {#4}
{ \exp_last_unbraced:NNo \macroa [ \l__macrob_key_tl ] {#3} {} }
{ \exp_last_unbraced:NNo \macroa [ \l__macrob_key_tl ] {#3} {#4} }
}
\group_end:
}%
}
\keys_define:nn { macrob }
{
key1 .tl_set:N = \l__macrob_key_tl
,key2 .bool_set:N = \l__macrob_key_bool
}
\ExplSyntaxOff
\begin{document}
\macrob*{expr}
\macrob[key1=val1]{expr}[opt]
\macrob[key2=true]{expr}[opt]
\macrob[key2=true]{expr}
\macrob{expr}[opt]
\macrob{expr}
\end{document}
结果
这三个看起来都一样:
答案3
包裹semantex
(免责声明:我是作者)是为了提供或多或少这种语法而制作的:
\documentclass{article}
\usepackage{semantex} % requires version 0.523,
% released on 2022/12/03
\NewVariableClass\MyVar[output=\MyVar, set arg slot={{\cdot}}]
\begin{document}
\( \MyVar{f}, \MyVar{f}{---} , \MyVar{f}[par=\Big]{---} , \MyVar{f}{x} \)
\NewObject\MyVar\vf{f}
\NewObject\MyVar\vx{x}
\( \vf , \vf{---}, \vf[par=\Big]{---}, \vf{x}, \vf{\vx} \)
\NewObject\MyVar\Gm{\Gamma}
\( \Gm, \Gm{ \frac{\beta}{2} }, \Gm[par=\Big]{ \frac{\beta}{2} } , \Gm{---} \)
\end{document}