执行嵌入宏时按字符解析参数

执行嵌入宏时按字符解析参数

我对逐个字符解析输入字符串并对每个字符执行某些操作很感兴趣。在此 MWE 中,我仅将 a 应用于\textbf每个连续字符作为示例,以验证解析器是否正常工作。

如果参数包含嵌入的宏,就会出现问题。如果我在参数中遇到宏,我想做的是停止解析并让宏执行,从原始参数流中消耗尽可能多的参数,然后继续解析参数的剩余部分。

虽然\charparse是解析宏,但我正在尝试设计一个辅助宏\execmacro来执行在参数流中检测到宏时需要执行的操作。在下面的 MWE 中,我可以做到这一点,但只有当我预先假设嵌入宏的性质时才如此。 具体来说,我展示了 3 个版本\execmacro,具体取决于我是否分别假定嵌入的宏需要 0、1 或 2 个参数。

我想要的是有一个\execmacro可以工作的版本,无论嵌入的宏需要多少个参数。

\documentclass{article}
\newcommand\charparse[1]{\charparsehelp#1\relax\relax\relax}
\def\charparsehelp#1#2\relax{%
  \ifcat\noexpand\relax\noexpand#1%
%   MACRO DETECTED IN INPUT STREAM
    \execmacro#1#2\relax% EXECUTE THE MACRO THEN RETURN TO PARSING 
  \else\textbf{#1}% BOLDING IS JUST AN EXAMPLE TO SHOW THAT PARSING MACRO IS WORKING
  \ifx\relax#2\else
    \charparsehelp#2\relax
  \fi\fi
}
\begin{document}
% THIS ONLY WORKS IF THE EMBEDDED MACRO TAKES EXACTLY ZERO ARGUMENTS
\def\execmacro#1#2\relax{#1\ifx\relax#2\else\charparsehelp#2\relax\fi}Case 1\par
\charparse{0123\itshape456\upshape789}\par
I would like the output from the above character-parsing macro to be\par
\charparse{0123}\itshape\charparse{456}\upshape\charparse{789}\medskip

% THIS ONLY WORKS IF THE EMBEDDED MACRO TAKES EXACTLY ONE ARGUMENT
\def\execmacro#1#2#3\relax{#1{#2}\ifx\relax#3\else\charparsehelp#3\relax\fi}Case 2\par
\charparse{0123\textit{456}789}\par
I would like the output from the above character-parsing macro to be\par
\charparse{0123}\textit{456}\charparse{789}\medskip

% THIS ONLY WORKS IF THE EMBEDDED MACRO TAKES EXACTLY TWO ARGUMENTS
\def\execmacro#1#2#3#4\relax{#1{#2}{#3}\ifx\relax#4\else\charparsehelp#4\relax\fi}Case 3\par
\charparse{0123\rule{3ex}{1ex}456789}\par
I would like the output from the above character-parsing macro to be\par
\charparse{0123}\rule{3ex}{1ex}\charparse{456789}
\end{document}

在此处输入图片描述

答案1

好吧,既然没人想碰这个,而且所有的专家都强烈反对,我可能应该放过它。但我不会。为了展示可以做什么,我遵循了 egreg 提到的提示,效果是“注册”可以处理的允许宏。

目前,不允许存在可选参数,这是一个缺点。出于演示目的,我将其设置为仅处理最多 2 个参数的宏。

它的工作原理如下。我希望开发\charparse{}一种每次解析一个字符的参数的方法。但关键是能够在参数中执行宏,然后在执行宏后继续逐个字符地解析。

如果宏没有参数,则不应注册。否则,必须使用 注册宏\registerparsemacro}{\<macroname>}{<arguments>}。重复一遍,不允许使用可选参数。因此,例如,我调用

\registerparsemacro{\textit}{1}
\registerparsemacro{\rule}{2}

此时,我可以在 的参数中包含\textit和的调用(不带可选参数) 。宏和不应注册,因为它们不带参数。\rule\charparse\itshape\upshape

以下 MWE 创建与问题显示的相同的输出。

\documentclass{article}
\usepackage{ifthen}
\newcommand\charparse[1]{\charparsehelp#1\relax\relax\relax}
\def\charparsehelp#1#2\relax{%
  \ifcat\noexpand\relax\noexpand#1%
%   MACRO DETECTED IN INPUT STREAM
    \execmacro#1#2\relax% EXECUTE THE MACRO THEN RETURN TO PARSING 
  \else\textbf{#1}% BOLDING IS JUST AN EXAMPLE TO SHOW THAT PARSING MACRO IS WORKING
  \ifx\relax#2\else
    \charparsehelp#2\relax
  \fi\fi
}
\newcounter{parsemacro}
\def\execmacro#1#2#3#4\relax{%
  \setcounter{parsemacro}{0}%
  \whiledo{\value{parsemacro} < \value{parsemacrocount}}{%
  \stepcounter{parsemacro}%
  \expandafter\expandafter\expandafter%
    \ifx\csname parsemacro\romannumeral\value{parsemacro}\endcsname#1%
      \if1\csname parsemacroarguments\romannumeral\value{parsemacro}\endcsname
        \execmacroONE#1{#2}#3#4\relax\else
      \if2\csname parsemacroarguments\romannumeral\value{parsemacro}\endcsname
        \execmacroTWO#1{#2}{#3}#4\relax\else
      \fi\fi
      \setcounter{parsemacro}{\numexpr\value{parsemacrocount}+1}%
    \fi
  }%
  \ifnum\value{parsemacro}=\value{parsemacrocount}\execmacroZERO#1#2#3#4\relax\fi
}
\def\execmacroZERO#1#2\relax{#1\ifx\relax#2\else
  \charparsehelp#2\relax\fi}
\def\execmacroONE#1#2#3\relax{#1{#2}\ifx\relax#3\else
  \charparsehelp#3\relax\fi}
\def\execmacroTWO#1#2#3#4\relax{#1{#2}{#3}\ifx\relax#4\else
  \charparsehelp#4\relax\fi}
\newcounter{parsemacrocount}
\setcounter{parsemacrocount}{0}
\def\registerparsemacro#1#2{%
  \stepcounter{parsemacrocount}%
  \expandafter\def\csname parsemacro\romannumeral\value{parsemacrocount}\endcsname{#1}%
  \expandafter\def\csname parsemacroarguments\romannumeral\value{parsemacrocount}%
    \endcsname{#2}%
}
\begin{document}
\registerparsemacro{\textit}{1}
\registerparsemacro{\rule}{2}
Case 1\par\charparse{0123\itshape456\upshape789}\par
I would like the output from the above character-parsing macro to be\par
\charparse{0123}\itshape\charparse{456}\upshape\charparse{789}\medskip

Case 2\par\charparse{0123\textit{456}789}\par
I would like the output from the above character-parsing macro to be\par
\charparse{0123}\textit{456}\charparse{789}\medskip

Case 3\par\charparse{0123\rule{3ex}{1ex}456789}\par
I would like the output from the above character-parsing macro to be\par
\charparse{0123}\rule{3ex}{1ex}\charparse{456789}
\end{document}

答案2

在提出这个问题近 5 年后,我意识到我最近的tokcycle包可以直接回答这个问题。我甚至让它处理可选参数,它假设当 a[跟随控制序列时会发生这些参数。(因此,要将正常放置[在控制序列之后,请 \macro{}[在输入流中使用,在控制序列之后放置空括号。)

在这里,它通过创建\Charparse留下控制序列、空格和(重要的) 组内容不受影响,因此仅对顶级字符标记进行操作。可以添加额外的筛选以将操作限制在特定的 catcode 上,或排除特定的字符标记。

因此,只要包含宏的参数{},它们就会不加修改地传递下去。

在下面的 MWE 中,前 3 个示例来自 OP 的问题。第 4 个示例显示了可选参数的正确处理。最后一个例子展示了如何克服将正常值[意外解释为可选参数的情况。

\documentclass{article}
\usepackage{tokcycle}
\newcommand\Charparse[1]{%
  \def\macON{F}%
  \def\optON{F}%
  \tokcycle
    {\if T\macON\ifx[##1\def\optON{T}\fi\fi
     \if T\optON\addcytoks{##1}\else\addcytoks{\textbf{##1}}\fi
     \ifx]##1\def\optON{F}\fi
     \def\macON{F}%
    }% CHARACTER DIRECTIVE
    {\addcytoks{##1}\gdef\macON{F}}% GROUP DIRECTIVE
    {\addcytoks{##1}\if F\optON\def\macON{T}\fi}% MACRO (CS) DIRECTIVE
    {\addcytoks{##1}\def\macON{F}}% SPACE DIRECTIVE
  {#1}\the\cytoks
}
\begin{document}
Tokcycle can define an environment that leaves\\
 macros, spaces, and group content untouched.\par
\Charparse{
0123\itshape456\upshape789

0123\textit{456}789

0123\rule{3ex}{1ex}456789

0123\rule[-3pt]{3ex}{1ex}456789

\S[x] \S{}[x]}
\end{document}

在此处输入图片描述

对于那些想知道的人来说,tokcycle可以指示处理组内的令牌(使用\processtoks{##1}而不是\addcytoks{##1}组指令)。然而,在这个应用程序中,它会违背目的

相关内容