LaTeX3 - 解析参数以应用特定的宏

LaTeX3 - 解析参数以应用特定的宏

在下面的代码中,我想解析\parseanduse诸如“实现”所示示例的参数。

每次o满足a时,应该使用以下整数来调用宏\macroO整数没有上限。

ra以及宏\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}

相关内容