在下面的代码中,我想解析\parseanduse
诸如“实现”所示示例的参数。
每次o
满足a时,应该使用以下整数来调用宏\macroO
。整数没有上限。
r
和a
以及宏\macroR
和具有类似的功能\macroA
。
在 LaTeX3 中实现这一目标的最佳方法是什么?
\documentclass[12pt,a4paper]{article}
\NewDocumentCommand{\macroO}{m}{Option [#1] ,}
\NewDocumentCommand{\macroR}{m}{Option $<$#1$>$ ,}
\NewDocumentCommand{\macroA}{m}{Argument #1 ,}
\NewDocumentCommand{\parseanduse}{m}{???}
\begin{document}
\parseanduse{o1r2a3}
same as
\macroO{1}\macroR{2}\macroA{3}
\bigskip
\parseanduse{o1r2a3o11r222a3333}
same as
\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}
\end{document}
答案1
您可以通过正则表达式进行解析。
\documentclass{article}
\ExplSyntaxOn
\NewDocumentCommand{\parseanduse}{m}
{
\projetmbc_parseanduse:n { #1 }
}
\NewDocumentCommand{\macroO}{m}
{
\projetmbc_macro_o:n { #1 }
}
\NewDocumentCommand{\macroR}{m}
{
\projetmbc_macro_r:n { #1 }
}
\NewDocumentCommand{\macroA}{m}
{
\projetmbc_macro_a:n { #1 }
}
\tl_new:N \l__projetmbc_parseanduse_tl
\cs_new_protected:Nn \projetmbc_parseanduse:n
{
\tl_set:Nn \l__projetmbc_parseanduse_tl { #1 }
\regex_replace_all:nnN
{ (o|r|a)([[:digit:]]*) } % search o or r or a followed by digits
{ \c{projetmbc_macro_\1:n} \cB\{\2\cE\} } % replace with macro and braces
\l__projetmbc_parseanduse_tl
\tl_use:N \l__projetmbc_parseanduse_tl
}
\cs_new_protected:Nn \projetmbc_macro_o:n
{
Option~[#1]~,
}
\cs_new_protected:Nn \projetmbc_macro_r:n
{
Option~$<$#1$>$~,
}
\cs_new_protected:Nn \projetmbc_macro_a:n
{
Argument~#1~,
}
\ExplSyntaxOff
\begin{document}
\parseanduse{o1r2a3}
same as
\macroO{1}\macroR{2}\macroA{3}
\bigskip
\parseanduse{o1r2a3o11r222a3333}
same as
\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}
\bigskip
\parseanduse{a1o2r3o11r222a3333}
same as
\macroA{1}\macroO{2}\macroR{3}\macroO{11}\macroR{222}\macroA{3333}
\end{document}
完全可扩展且易读的版本。
\documentclass{article}
\ExplSyntaxOn
% these are just to check the output
\NewDocumentCommand{\macroO}{m}
{
\projetmbc_macro_o:n { #1 }
}
\NewDocumentCommand{\macroR}{m}
{
\projetmbc_macro_r:n { #1 }
}
\NewDocumentCommand{\macroA}{m}
{
\projetmbc_macro_a:n { #1 }
}
% the main command
\NewExpandableDocumentCommand{\parseanduse}{m}
{
\projetmbc_parseanduse:n { #1 }
}
% we assume that the argument starts with a letter
% error checks can be added
% \q_nil is appended to the list
\cs_new:Nn \projetmbc_parseanduse:n
{
\__projetmbc_parseanduse_start:N #1 \q_nil
}
\cs_new:Nn \__projetmbc_parseanduse_start:N
{% #1 is a letter, start with empty second argument and scan the next token
\__projetmbc_parseanduse_next:nnN { #1 } { }
}
\cs_new:Nn \__projetmbc_parseanduse_next:nnN
{
\quark_if_nil:NTF #3
{% we reached the end, issue the macro and the collected argument
\use:c { projetmbc_macro_#1:n } { #2 }
}
{% scan the next token
\int_compare:nTF { `0 <= `#3 <= `9 }
{% we found a digit, append it to the candidate second argument
\__projetmbc_parseanduse_next:nnN { #1 } { #2#3 }
}
{% we found a letter, issue the macro and the collected argument
\use:c { projetmbc_macro_#1:n } { #2 }
% restart the recursion
\__projetmbc_parseanduse_start:N #3
}
}
}
\cs_new_protected:Nn \projetmbc_macro_o:n
{
Option~[#1]~,
}
\cs_new_protected:Nn \projetmbc_macro_r:n
{
Option~$<$#1$>$~,
}
\cs_new_protected:Nn \projetmbc_macro_a:n
{
Argument~#1~,
}
\ExplSyntaxOff
\begin{document}
\parseanduse{o1r2a3}
same as
\macroO{1}\macroR{2}\macroA{3}
\bigskip
\parseanduse{o1r2a3o11r222a3333}
same as
\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}
\bigskip
\parseanduse{a1o2r3o11r222a3333}
same as
\macroA{1}\macroO{2}\macroR{3}\macroO{11}\macroR{222}\macroA{3333}
\edef\test{\parseanduse{a1o2r3o11r222a3333}}
\texttt{\meaning\test}
\end{document}
在最后几行中,宏\projetmbc_macro_(o|r|a):n
没有被展开,因为它们是受保护的。但如果它们是完全可展开的,就不会有问题。
答案2
以下是相当低级的代码,但展示了如何使用完全可扩展的循环来实现这一点。循环可以做的比你最初要求的更多(它确实适用于各个宏的参数中的空格和括号部分)。
\documentclass[12pt,a4paper]{article}
\NewDocumentCommand{\macroO}{m}{Option [#1] ,}
\NewDocumentCommand{\macroR}{m}{Option $<$#1$>$ ,}
\NewDocumentCommand{\macroA}{m}{Argument #1 ,}
\ExplSyntaxOn
\msg_new:nnn { projetmbc } { unknown-function }
{ Unknown~ function~ `#1'.~ Giving~ up. }
\scan_new:N \s__projetmbc_stop
\scan_new:N \s__projetmbc_mark
\cs_new:Npn \__projectmbc_gobble_to_stop:w #1 \s__projetmbc_stop {}
\cs_new:Npn \__projectmbc_gobble_to_mark:w #1 \s__projetmbc_mark {}
\cs_new:Npn \__projetmbc_brace_open: { { \if_false: } \fi: }
\cs_new:Npn \__projetmbc_brace_close: { \if_false: { \fi: } }
\cs_new:Npn \projetmbc_parse_loop:n #1
{
\use:e
{ \__projetmbc_parse_loop:N #1 {\s__projetmbc_mark}~ \s__projetmbc_stop }
}
\cs_new:Npn \__projetmbc_parse_loop:N #1
{
\cs_if_exist:cTF { macro \str_uppercase:n {#1} }
{
\exp_not:c { macro \str_uppercase:n {#1} }
\__projetmbc_brace_open:
\__projetmbc_parse_loop_collect:w
}
{
\msg_expandable_error:nnn { projetmbc } { unknown-function } {#1}
\__projectmbc_gobble_to_stop:w
}
}
\cs_new:Npn \__projetmbc_parse_loop_collect:w #1 \s__projetmbc_stop
{
\tl_if_head_is_group:nTF {#1}
\__projetmbc_parse_loop_group:nw
{
\tl_if_head_is_space:nTF {#1}
\__projetmbc_parse_loop_space:w
\__projetmbc_parse_loop_normal:Nw
}
#1 \s__projetmbc_stop
}
\cs_new:Npn \__projetmbc_parse_loop_group:nw #1
{
\__projectmbc_gobble_to_mark:w
#1 \__projetmbc_parse_loop_final:w \s__projetmbc_mark
{ \exp_not:n {#1} }
\__projetmbc_parse_loop_collect:w
}
\use:n { \cs_new:Npn \__projetmbc_parse_loop_space:w } ~
{ ~ \__projetmbc_parse_loop_collect:w }
\cs_new:Npn \__projetmbc_parse_loop_normal:Nw #1
{
\cs_if_exist:cTF { macro \str_uppercase:n {#1} }
{
\__projetmbc_brace_close:
\exp_not:c { macro \str_uppercase:n {#1} }
\__projetmbc_brace_open:
}
{ \exp_not:n {#1} }
\__projetmbc_parse_loop_collect:w
}
\cs_new:Npn \__projetmbc_parse_loop_final:w #1 \s__projetmbc_stop
{ \__projetmbc_brace_close: }
\NewExpandableDocumentCommand \parseanduse { m }
{ \projetmbc_parse_loop:n {#1} }
\ExplSyntaxOff
\usepackage{xcolor}
\begin{document}
\parseanduse{o1r2a3}
same as
\macroO{1}\macroR{2}\macroA{3}
\bigskip
\parseanduse{o1r2a3o11r222a3333}
same as
\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}
\parseanduse{o\textcolor{red}{abc}1r2a3}
same as
\macroO{\textcolor{red}{abc}1}\macroR{2}\macroA{3}
\end{document}
此外,还有一个简化版本,仅适用于原始输入语法(仅限N
-type 参数):
\documentclass[12pt,a4paper]{article}
\NewDocumentCommand{\macroO}{m}{Option [#1] ,}
\NewDocumentCommand{\macroR}{m}{Option $<$#1$>$ ,}
\NewDocumentCommand{\macroA}{m}{Argument #1 ,}
\ExplSyntaxOn
\msg_new:nnn { projetmbc } { unknown-function }
{ Unknown~ function~ `#1'.~ Giving~ up. }
\scan_new:N \s__projetmbc_stop
\scan_new:N \s__projetmbc_mark
\cs_new:Npn \__projectmbc_gobble_to_stop:w #1 \s__projetmbc_stop {}
\cs_new:Npn \__projectmbc_gobble_to_mark:w #1 \s__projetmbc_mark {}
% these two macros will leave one unmatched brace when fully expanded, this
% works because TeX doesn't check for brace balancing when gobbling the
% remainder of an \if-construct.
\cs_new:Npn \__projetmbc_brace_open: { { \if_false: } \fi: }
\cs_new:Npn \__projetmbc_brace_close: { \if_false: { \fi: } }
\cs_new:Npn \projetmbc_parse_loop:n #1
{
% the loop works inside an expansion context which is started here, as the
% end marker we use \s__projetmbc_mark, and as the terminal marker we use
% \s__projetmbc_stop (to gobble the remainder of the current iteration)
\use:e
{ \__projetmbc_parse_loop:N #1 \s__projetmbc_mark \s__projetmbc_stop }
}
% the first iteration differs from consecutive ones. It is enforced that the
% argument starts with one of the macros, else an error is thrown and the
% remainder gobbled
\cs_new:Npn \__projetmbc_parse_loop:N #1
{
% check whether macro exists
\cs_if_exist:cTF { macro \str_uppercase:n {#1} }
{
% if so build the name and leave an unmatched opening brace (since we're
% in an expansion context this does no harm as we will have it closed
% when we're done)
\exp_not:c { macro \str_uppercase:n {#1} }
\__projetmbc_brace_open:
\__projetmbc_parse_loop_aux:N
}
{
\msg_expandable_error:nnn { projetmbc } { unknown-function } {#1}
\__projectmbc_gobble_to_stop:w
}
}
\cs_new:Npn \__projetmbc_parse_loop_aux:N #1
{
% all following iterations must check whether the loop is done. This is done
% in a very fast manner. Assuming that no valid user input will contain the
% end marker \s__projetmbc_mark, #1 can only be exactly that token when
% we're done. So in that case only #1 is gobbled and the final action
% executed, else everything until the first \s__projetmbc_mark is gobbled.
\__projectmbc_gobble_to_mark:w
#1 \__projetmbc_parse_loop_final:w \s__projetmbc_mark
\cs_if_exist:cTF { macro \str_uppercase:n {#1} }
{
% if the current token is one of the macro starting ones we need to
% close the argument of the preceding macro. Afterwards build the new
% macro and start its argument.
\__projetmbc_brace_close:
\exp_not:c { macro \str_uppercase:n {#1} }
\__projetmbc_brace_open:
}
% everything else should just remain there, we protect it from further
% expanding.
{ \exp_not:n {#1} }
% call the next iteration
\__projetmbc_parse_loop_aux:N
}
% the final action needs to close the argument of the last macro after removing
% the remainder of the last iteration from the input
\cs_new:Npn \__projetmbc_parse_loop_final:w #1 \s__projetmbc_stop
{ \__projetmbc_brace_close: }
\NewExpandableDocumentCommand \parseanduse { m }
{ \projetmbc_parse_loop:n {#1} }
\ExplSyntaxOff
\begin{document}
\parseanduse{o1r2a3}
same as
\macroO{1}\macroR{2}\macroA{3}
\bigskip
\parseanduse{o1r2a3o11r222a3333}
same as
\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}
\end{document}
第二个版本的结果:
答案3
使用令牌循环就很容易了。
\documentclass[12pt,a4paper]{article}
\NewDocumentCommand{\macroO}{m}{Option [#1] ,}
\NewDocumentCommand{\macroR}{m}{Option $<$#1$>$ ,}
\NewDocumentCommand{\macroA}{m}{Argument #1 ,}
\usepackage{tokcycle}
\newcommand\parseanduse[1]{\tokcycle{%
\ifx o##1\def\z{}\parsearg{O}\else
\ifx r##1\def\z{}\parsearg{R}\else
\ifx a##1\def\z{}\parsearg{A}\fi\fi\fi
}{}{}{}{#1}\the\cytoks}
\def\parsearg#1{%
\tcpopappto\z
\tcpeek\zz
\tctestifcatnx 0\zz
{\parsearg{#1}}%
{\csname macro#1\expandafter\endcsname\expandafter{\z}}}
\begin{document}
\parseanduse{o1r2a3}
same as
\macroO{1}\macroR{2}\macroA{3}
\bigskip
\parseanduse{o1r2a3o11r222a3333}
same as
\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}
\end{document}
如果采用宏后缀字母与简写字母大小写相同的约定(此处为小写),则标记循环将更加简单:
\documentclass[12pt,a4paper]{article}
\NewDocumentCommand{\macroo}{m}{Option [#1] ,}
\NewDocumentCommand{\macror}{m}{Option $<$#1$>$ ,}
\NewDocumentCommand{\macroa}{m}{Argument #1 ,}
\usepackage{tokcycle}
\newcommand\parseanduse[1]{%
\tokcycle{\def\z{}\parsearg{##1}}{}{}{}{#1}\the\cytoks}
\def\parsearg#1{%
\tcpopappto\z
\tcpeek\zz
\tctestifcatnx 0\zz
{\parsearg{#1}}%
{\csname macro#1\expandafter\endcsname\expandafter{\z}}}
\begin{document}
\parseanduse{o1r2a3}
same as
\macroo{1}\macror{2}\macroa{3}
\bigskip
\parseanduse{o1r2a3o11r222a3333}
same as
\macroo{1}\macror{2}\macroa{3}\macroo{11}\macror{222}\macroa{3333}
\end{document}
答案4
仅供比较,您只能使用 TeX 原语来执行以下操作:
\def\macroO#1{Option [#1], }
\def\macroR#1{Option $<$#1$>$, }
\def\macroA#1{Argument #1, }
\def\parseanduse#1{\parseanduseX#1or}
\def\parseanduseX o{\parseanduseO}
\def\parseanduseO #1r{\ifx^#1^\else\macroO{#1}\expandafter\parseanduseR\fi}
\def\parseanduseR #1a{\macroR{#1}\parseanduseA}
\def\parseanduseA #1o{\macroA{#1}\parseanduseO}
% Test:
\parseanduse{o1r2a3o11r222a3333}
好的,以下第二个示例以任意顺序接受“命令”o,r,a,并且它是完全可扩展的,并且仅使用 TeX 基元。我们不需要 Expl3。
\def\afterfi#1#2\fi{\fi#1}
\def\isdigit #1#2{%
\ifnum #21\else0\fi=\ifnum`#1>`9 0\else \ifnum`#1<`0 0\else 1\fi\fi\space
}
\def\scandigits#1#2#3{\isdigit#3\iftrue
\afterfi{\scandigits#1{#2#3}}\else\afterfi{#1{#2}#3}\fi
}
\def\parseanduse#1{\parseanduseX#1E}
\def\parseanduseX#1{\ifx E#1\else
\afterfi{\expandafter\scandigits\csname macro#1\endcsname{}}\fi
}
\def\macroo#1{\macroO{#1}\parseanduseX}
\def\macror#1{\macroR{#1}\parseanduseX}
\def\macroa#1{\macroA{#1}\parseanduseX}
\def\macroO#1{Option [#1], }
\def\macroR#1{Option $<$#1$>$, }
\def\macroA#1{Argument #1, }
\parseanduse{o1r2a3o11r222a3333a13}