我对逐个字符解析输入字符串并对每个字符执行某些操作很感兴趣。在此 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}
组指令)。然而,在这个应用程序中,它会违背目的