问题。
我正在尝试编写一个类似于的命令\newcommand
,但遇到了问题非法参数个数:由于我无法解决的原因,它要求我使用类型的参数,##1
而不是#1
。我熟悉在嵌套定义的情况下使用前者,例如在诸如 这样的代码片段中\def\foo#1{\def\bar##1{#1-##1}}
,但我不明白为什么在我尝试执行的操作中会出现错误。我希望有人可以向我展示如何使样式的参数#1
足够,如下面我描述的示例中所示。
细节。
我正在尝试编写一个名为 的宏\newmacro
,它模拟 的语法\newcommand
,并使每个宏在第一次使用时将其自己的定义写入输出文件。尝试下面的最小示例以准确了解我的意思:
\documentclass{article}
\makeatletter
\newwrite\macro@outfile
\immediate\openout\macro@outfile=\jobname.macros%
\typeout{Writing extra macros to file \jobname.macros}%
\def\macro@write#1#2{%
\immediate\write\macro@outfile{\string\renewcommand\string#1#2}%
\renewcommand#1#2%
#1%
}
\def\newmacro#1[#2]#3{%
\def#1{\macro@write{#1}{[#2]{#3}}}}
\makeatother
这些宏定义不能正常工作:例如,使用上面的前言,\newmacro
下面的第二次调用会引发错误。
\begin{document}
\newmacro\testmacro[1]{foo(##1)}
\testmacro{1}
\testmacro{2}
\newmacro\testmacro[1]{foo --- #1 ---}
\testmacro{3}
\testmacro{4}
\end{document}
如果本文档按照上述序言进行编译test.tex
,则除了通常的输出外,test.macros
还会创建一个新文件,其内容如下:
\renewcommand\testmacro[1]{foo(##1)}
\renewcommand\testmacro[1]{foo --- ##1 ---}
这几乎是正确的,这就是我所说的宏将“自己的定义”写入输出文件的意思。但是,我希望存储的定义使用参数#1
而不是,并且使用参数##1
调用不会抛出错误。我希望有人能告诉我如何做到这一点。\newmacro
#1
答案1
当你说:
\newmacro\testmacro[1]{foo --- #1 ---}
你最终会得到:
\def\testmacro{\macro@write{\testmacro}{[1]{foo --- #1 ---}}}}
因此,您尝试定义一个没有任何参数的宏,但仍在#1
其中使用。这会导致Illegal parameter number
错误消息。
为了允许#1
、#2
等,您需要使用正确数量的参数定义宏。您可以先使用九个参数定义宏以避免任何Illegal parameter number
错误。然后使用##1
等作为参数来扩展它,因此您基本上#1
用##1
、 等替换,并将其定义为相同的宏但没有参数。因为所有这些都发生在另一个宏中,所以必须将\newmacro
所有这些#
都加倍:
\def\newmacro#1[#2]#3{%
\def#1##1##2##3##4##5##6##7##8##9{\macro@write{#1}{[#2]{#3}}}% Define macro in a safe way
\expandafter\def\expandafter#1\expandafter{#1{####1}{####2}{####3}{####4}{####5}{####6}{####7}{####8}{####9}}% Replace `#1` with `##1` etc.
}
但是,输出文件中会出现 的问题##
。最好通过再次定义具有九个参数的宏并打印去除前缀的 来避免这种情况\meaning
,然后再将其定义为其真实定义:
\def\macro@write#1#2{%
\def#1##1##2##3##4##5##6##7##8##9{#2}%
\immediate\write\macro@outfile{\string\renewcommand\string#1\expandafter\strip@prefix\meaning#1}%
\renewcommand#1#2%
#1%
}
该\jobname.macros
文件包含:
\renewcommand\testmacro[1]{foo(#1)}
\renewcommand\testmacro[1]{foo --- #1 ---}
但是,当您现在##1
在宏定义中使用时,这种情况就会发生,例如:
\newmacro\testmacro[1]{foo --- #1 ---\def\A##1{(##1)}}
因此,您建议采用以下方法:将宏定义为不同的名称,然后将该定义复制到 内的真实名称\macro@write
。这样,您就可以避免宏参数转义的所有问题。您可以使用\csname
with\string#1
构建第二个宏名称,该名称与官方宏名称不同。
\documentclass{article}
\makeatletter
\newwrite\macro@outfile
\immediate\openout\macro@outfile=\jobname.macros%
\typeout{Writing extra macros to file \jobname.macros}%
\def\newmacro#1[#2]#3{%
\expandafter\newcommand\csname @\string#1\endcsname[#2]{#3}%
\newcommand#1{\macro@write{#1}{#2}}%
}
\def\macro@write#1#2{%
\expandafter\let\expandafter#1\csname @\string#1\endcsname
\expandafter\let\csname @\string#1\endcsname\@undefined
\immediate\write\macro@outfile{\string\newcommand\string#1[#2]{\expandafter\strip@prefix\meaning#1}}%
#1%
}
\makeatother
\begin{document}
\newmacro\testmacroa[1]{foo(#1)}
\testmacroa{1}
\testmacroa{2}
\newmacro\testmacrob[1]{foo --- #1 ---}
\testmacrob{3}
\testmacrob{4}
\newmacro\testmacroc[1]{foo --- #1 ---\def\A##1{(##1)}}
\testmacroc{3}
\testmacroc{4}
\end{document}
我擅自用 替换了 ,\renewcommand
因为\newcommand
对于宏来说,这似乎更合乎逻辑\newmacro
。如果您确实想要\renewcommand
,即允许\newmacro
重新定义其宏,则定义\newmacro
如下:
\def\newmacro#1[#2]#3{%
\expandafter\let\csname @\string#1\endcsname\empty
\expandafter\renewcommand\csname @\string#1\endcsname[#2]{#3}%
\def#1{\macro@write{#1}{#2}}%
}
\renewcommand
第一行确保宏在使用之前无论如何都存在。
答案2
结语。
正如我在对 Martin 的回答的评论中指出的那样,当我尝试扩展我的/他的代码以接受 样式的可选参数时,我发现了其他障碍\newcommand
。我接受了 Martin 的回答,认为它解决了我发布的问题:此答案旨在展示如何成功处理使用 定义的宏的两种情况\newcommand
,包括带有可选参数和不带有可选参数的情况。
恢复并写入宏的预期含义。
基本问题是,对于用于\cseq
接受可选参数的宏,\newcommand
定义了两个宏:\cseq
它本身,它查找可选参数,\\cseq
它实际上对参数进行操作。因此,我们需要付出额外的努力来提取“含义”,\cseq
以便将其写入文件\jobname.macros
。
我们定义一个单独的宏来“激活”一个宏\cseq
,该宏的定义是\newmacro
——赋予\cseq
其预期含义,将其临时存储在宏中\macro@cseq
,同时还将宏的“名称”提取到\@tempa
。
\def\macro@activate#1{%
\edef\@tempa{\expandafter\@gobble\string#1}%
\expandafter\let\expandafter#1\csname macro@\@tempa\endcsname
\expandafter\let\csname macro@\@tempa\endcsname\@undefined
}
我们假设宏的名称(例如 cseq
) 存储在\@tempa
我们想要将 的预期含义写入\cseq
输出文件时。然后,以下命令构造命令序列\macro@cseq
,并将其存储为 的新含义\@tempa
。然后,使用这个命令序列,它将 的含义\\macro@cseq
(实际作用于 的参数的宏\macro@cseq
)写入输出:最后一行上的宏\\macro@cseq
本质上构造为 \csname\string\macro@cseq\endcsname
。
\def\macro@write@opts#1[#2][#3]{%
\edef\@tempa{\expandafter\noexpand\csname macro@\@tempa\endcsname}%
\def\@tempb##1{%
\immediate\write\macro@outfile{\string\newcommand\string#1[#2][#3]{%
\expandafter\strip@prefix\meaning##1}}}%
\expandafter\expandafter\expandafter\@tempb%
\expandafter\csname\expandafter\string\@tempa\endcsname%
}
相应的编写不带参数或不带可选参数的宏的命令更简单。在下面的宏中,#2
等于空字符串或[numargs]
。
\def\macro@write@noopts#1#2{%
\immediate\write\macro@outfile{%
\string\newcommand\string#1#2{\expandafter\strip@prefix\meaning#1}}%
}
自激活宏的初始定义。
代码的其余部分是一个或多或少简单的案例分析,它确定所\cseq
定义的宏是否有任何参数,是否有任何可选参数,然后重新定义\cseq
以激活(并写入文件)其预期行为,然后再调用自身。在激活其预期行为的过程中,\@tempa
用于存储宏的名称(例如 cseq
),正如我们在宏采用可选参数的情况下所要求的那样。
\def\newmacro#1{%
\edef\@tempa{\expandafter\@gobble\string#1}%
\kernel@ifnextchar [{\newmacro@args#1}{\newmacro@noopts#1{}}}
\def\newmacro@args#1[#2]{%
\kernel@ifnextchar [{\newmacro@opts#1{[#2]}}{\newmacro@noopts#1{[#2]}}}
\def\newmacro@noopts#1#2#3{%
\expandafter\newcommand\csname macro@\@tempa\endcsname#2{#3}%
\def#1{%
\macro@activate#1%
\macro@write@noopts#1{#2}%
#1}}
\def\newmacro@opts#1#2[#3]#4{%
\expandafter\newcommand\csname macro@\@tempa\endcsname#2[#3]{#4}%
\def#1{%
\macro@activate#1%
\macro@write@opts#1#2[#3]%
#1}}
再次感谢马丁的帮助。