宏参数由多个分隔符分隔

宏参数由多个分隔符分隔

TeX 用户可以简单地创建带有由任意标记序列分隔的参数的宏。例如

\def\macro #1delimiter{...#1...}

是否可以创建一个具有多个分隔符的参数的宏?我的意思是这样的:

\mydef\macro #1[first or second]{...#1...}
\macro text first  % -> ...text ... (delimiter is first)
\macro text second % -> ...text ... (delimiter is second)

参数文本应从分隔符列表中扫描到第一个分隔符。无需再扫描。我知道您可以在此之后设置一个固定分隔符(例如 \par),然后使用这个较长的参数进行处理并从中读取单词“first”或“second”。但这种方法读取的输入数据比我们需要的要多。进行了不必要的标记化,也许这里是不平衡的文本等。

答案1

您可以\seplist完全按照问题中描述的目的使用我的宏。该宏\seplist具有以下语法:

\def\yourmacro#1{... macro with normal #1 without separator}
\seplist{list of separators}\yourmacro parameter text

例如:

\def\macro#1{parameter is: "#1"}
\def\separed{\seplist{{sepA}{SepB}{SEPC}}}

\separed\macro text separated by sepA % sepA is separator
\separed\macro text separated by SepB % SepB is separator
\separed\macro text separated by SEPC % SEPC is separator

实际使用的分隔符被全局存储在\sepused宏中。宏程序员可以使用它。

输入流读取到列出的任何分隔符的第一个实例,不再有。分隔符列表包括括号中的分隔符。如果只有一个标记分隔符,则可以省略括号。示例:

\seplist{0123456789}\macro text to the first decimal digit 7

分隔符可以由除{ }和之外的任何标记组成#(更确切地说,这些字符的类别起着作用)。这与\def使用带有单个分隔符的原始字符时相同。参数文本可以包含这些字符,但必须始终保持平衡。因此,隐藏在括号中的分隔符将被忽略。此行为与普通分隔参数类似。示例:

\seplist{0123456789}\macro this text {1234} is separated by five: 5
\seplist{\undefined \defined}\macro this text ends by \undefined or \defined
\seplist{{\undefined\defined}\par}\macro this text ends by \undefined\defined or by \par

如果有多个分隔符,它们都匹配相同的文本,则较长的分隔符获胜。示例:

\def\m#1{\message{param: "#1", separator: "\sepused"}}
\seplist{{BC}{ABC}}\m ahaABC % -> param: "aha", separator: "ABC"

如果您的\macro定义为\long\par可以扫描到参数中。警告:如果您的\macro未定义为\long则参数扫描不会停止在\par!您必须将 添加\par到分隔符列表中。示例:

\def\parinmacro{\par}
\def\thismacro{\ifx\sepused\parinmacro \message{something wrong}\fi ...}
\seplist{{ab}{cd}\par}\thismacro This text skips the hidden {ab} and {cd}
                                 and it stops at the end of the paragraph.

您可以使用\sepdef与您的要求类似的语法定义宏:

\def\sepdef #1#2[#3]{\def#1{\seplist{#3}{\csname.\string#1\endcsname}}%
  \long\expandafter\def\csname.\string#1\endcsname ##1}

\sepdef\test #1[{first}{second}]{Parameter is: "#1", separator is "\sepused".}

\test text separated by first
\test text separated by second

宏的实现\seplist如下。它仅使用原语和基本宏。它应该适用于任何 TeX 格式。

\long\def\addto#1#2{\expandafter\def\expandafter#1\expandafter{#1#2}}
\newtoks\seplistT

\long\def\seplistD#1{%
   \seplistS##2\seplistE{\def\tmpa{##1}\def\tmpb{##2}\seplistE}%
   \def\tmpb{\tmpa #1}\expandafter\tmpb \tmp\seplistD\seplistE
}
\long\def\seplistE#1{%
   \ifx\tmpa\empty
      \seplistS\seplistD{\def\tmpb{##1}}\expandafter\tmpa\tmpb
      \ifx\tmpb\empty \seplistQ{#1}%
      \else \expandafter\addto\expandafter\seplistLx
                           \expandafter {\expandafter\seplistD\expandafter{\tmpb}{#1}}%
   \fi\fi
}
\def\seplistS{\long\expandafter\def\expandafter\tmpa\expandafter##\expandafter1\tmp}
\long\def\seplistQ#1#2\seplistA{\fi\fi\gdef\sepused{#1}\seplistZ}

\long\def\seplist#1#2{\begingroup
  \toks0={#2}\let\bgroup=\relax \let\egroup=\relax
  \def\seplistL{}\def\seplistLx{}\seplistI#1{}\gdef\sepused{}%
  \ifx\seplistL\empty \expandafter\endgroup \the\toks0\else
  \seplistT={}\expandafter\seplistA\fi
}
\def\seplistA{\futurelet\tmp\seplistB}
\def\seplistB{\let\next=\seplistP
   \expandafter\ifx\space\tmp \let\next=\seplistC \let\nexxt=\seplistM \fi
   \ifx##\tmp \let\next=\seplistC \let\nexxt=\seplistH \fi
   \ifx{\tmp  \let\next=\seplistG \fi
   \ifx}\tmp  \let\next=\seplistC \let\nexxt=\seplistF \fi
   \next
}
\def\seplistC{\afterassignment\nexxt \let\next= }
\long\def\seplistP#1{\seplistX#1\def\tmp{#1}\seplistN}
\def\seplistM{\seplistX{ }\def\tmp{ }\seplistN}
\def\seplistH{\seplistX{##}\def\seplistLx{}\seplistA}
\def\seplistN{\edef\seplistLx{\expandafter}\seplistLx \seplistL \seplistA}
\long\def\seplistG#1{\def\seplistLx{}\seplistX{{#1}}\seplistA}
\def\seplistF{\seplistT\expandafter{\expandafter{\the\seplistT}}\seplistZ}
\long\def\seplistX#1{\seplistT\expandafter{\the\seplistT#1}}
\def\seplistZ{\let\tmp=\sepused 
   \expandafter\seplistS\expandafter{\the\toks0{##1}}%
   \expandafter\expandafter\expandafter\endgroup\expandafter\tmpa\the\seplistT
}
\long\def\seplistI#1{\ifx\seplistI#1\seplistI\else
   \addto\seplistL{\seplistD{#1}{#1}}\expandafter\seplistI \fi
}

对实施的评论

我们以类似方式读取参数 token-per-token此主题并将这些标记存储在\seplistT标记列表中。内部宏\seplistL包含以下形式的分隔符列表:

\seplistD{sepA}{sepA}\seplistD{sepB}{sepB}\seplistD{sepC}{sepC}...

我们将已经读取的标记存储到\tmp并运行\seplistL。更确切地说:在开始时,临时文件\seplistLx为空。对于每个读取的标记,我们将 \seplistLx和扩展\seplistL至输入流,并在执行之前重置\def\seplistLx{}。现在,执行输入流,即, \seplistD为每个分隔符处理宏。的任务 \seplistD{sepA}{sepA}如下:测试是否\tmp等于其第一个参数的第一个标记(s在此示例中为)。如果为真,则\seplistD(使用\seplistE)将文本\seplistD{epA}{sepA}(从第一个参数中删除第一个标记) 添加到\seplistLx将为下一个标记执行的临时列表中。如果\tmp不等于第一个参数的第一个标记,\seplistD则不执行任何操作。

例如,中的下一个读取标记\tmpe。然后\seplistD{epA}{sepA} 将 保存到\seplistD{pA}{sepA}\seplistLx因为第一个字母是e。如果 中的下一个标记\tmpp\seplistD{A}{sepA}则将 存储到\seplistLx。最后,如果 中的下一个标记\tmpA,则不 \seplistD{A}{sepA}存储\seplistD{}{sepA},但它判定找到了分隔符,因为第一个参数为空。它将 定义\sepused 为其第二个参数( ),并通过 加 来sepA结束此游戏。如果 中的最后一个标记不是,则不执行任何操作,并且链被断开,因为在每个步骤中都设置为空。可以构建新链,因为仍然包含在 中,在计算过程中不会更改。\seplistQ\seplistZ\tmpA\seplistD{A}{sepA}\seplistLx\seplistD{sepA}{sepA}\seplistL

TeX 中的宏编程非常漂亮,但它与“普通”编程所使用的传统技术不同。我们在这里利用了代码可以创建代码的事实,即数据和代码并非严格分离。

相关内容